Java—多線程基礎

多線程基礎

基本概念

進程

所謂進程就是運行在操做系統的一個任務,進程是計算機任務調度的一個單位,操做系統在啓動一個程序的時候,會爲其建立一個進程,JVM就是一個進程。進程與進程之間是相互隔離的,每一個進程都有獨立的內存空間。java

計算機實現併發的原理是:CPU分時間片,交替執行,宏觀並行,微觀串行。同理,在進程的基礎上分出更小的任務調度單元就是線程,咱們所謂的多線程就是一個進程併發多個線程。數據庫

線程

在上面咱們提到,一個進程能夠併發出多個線程,而線程就是最小的任務執行單元,具體來講,一個程序順序執行的流程就是一個線程,咱們常見的main就是一個線程(主線程)。多線程

線程的組成

想要擁有一個線程,有這樣的一些不可或缺的部分,主要有:CPU時間片,數據存儲空間,代碼。併發

CPU時間片都是有操做系統進行分配的,數據存儲空間就是咱們常說的堆空間和棧空間,在線程之間,堆空間是多線程共享的,棧空間是互相獨立的,這樣作的好處不只在於方便,也減小了不少資源的浪費。代碼就不作過多解釋了,沒有代碼搞個毛的多線程。工具

線程的建立和啓動
傳統建立線程有兩種方式
  1. 繼承Thread類,覆蓋run方法
  2. 實現Runnable接口,覆蓋run方法
Runnable並非線程對象,而是一個任務對象。那麼Runnable和Thread有什麼樣的關係呢?

經過查閱API,咱們發現建立一個線程除了使用Thread的無參構造方法之外有一個有參構造方法是這樣 :Thread(Runnable target),經過這個方法會分配一個新的Thread 對象。spa

其中的參數是一個類型爲Runnable的target屬性。操作系統

Runnable接口最大的做用就是爲非Thread子類的類提供了一種實現線程的方式,只須要實現Runnable接口就能夠藉助Thread建立一個線程;另外一方面,若是隻想重寫run方法,不想獲得其餘的Thread的方法,實現Runnable是一個好的選擇。線程

JDK1.5

線程池code

ExecutorService(線程池 interface)對象

//經過工具類中的方法可以新建一個線程池,用ExecutorService接受
ExecutorService es = Executors.newFixedThreadPool(2);

Callable對象

相似於Runnable(描述任務的interface)。
//建立一個Callable的實現類
Callable<Integer> task1 = new Callable<Integer>(){
  public Integer call() throws Exception{
    int result = 0;
    for(int i=2;i<=100;i+=2){
      result += i;
      Thread.sleep;
    }
      return result;
  }
  
}

//用Future對象接收fask1的返回值  將任務提交給線程池
Future<Integer> f = es.submit(task1);
//經過get方法獲取Future中的值 在這個時候主線程主動的調取get  若是分支線程尚未結束,主線程會在這裏阻塞
int result = f1.get();
//關閉線程池
es.shutdown();

從以上這段代碼咱們能夠看到不少不同的地方,首先在Callable對象中是能夠拋出異常的,其次有返回值,在這個基礎上也就引出了一個新的問題,若是接收該線程的對象?JDK1.5中也給出瞭解決的方法是Future對象.

啓動線程

在這裏咱們須要明白,上面兩種方式並不會讓咱們獲得真正的線程,只是獲得了線程對象,只有啓動線程,纔算獲得了真正的線程。

經過執行start()方法可以啓動一個線程,可是啓動線程並非當即執行,成功啓動的線程會處於就緒狀態,何時執行須要等到拿到時間片以後。

線程的分類

用戶線程和守護(Daemon)線程。

守護線程:守護線程會一直運行,直到其餘非守護線程都結束的時候,纔會結束。有一個典型的守護線程就是:垃圾回收線程,和虛擬機共存亡,直到虛擬機中沒有任何線程的時候虛擬機關閉的時候纔會終止,簡單說就是虛擬機在,它就在,虛擬機亡便亡。

線程的狀態

線程的狀態

上面咱們提到過,一個線程在啓動以後不會立馬執行,而是處於就緒狀態(Ready),就緒狀態就是線程的狀態的一種,處於這種狀態的線程意味着一切準備就緒, 須要等待系統分配到時間片。爲何沒有立馬運行呢,由於同一時間只有一個線程可以拿到時間片運行,新線程啓動的時候讓它啓動的線程(主線程)正在運行,只有等主線程結束,它纔有機會拿到時間片運行。

線程的狀態:初始狀態(New),就緒狀態(Ready),運行狀態(Running)(特別說明:在語法的定義中,就緒狀態和運行狀態是一個狀態Runable),等待狀態(Waitering),終止狀態(Terminated)

RUNNABLE),等待狀態(Waitering),終止狀態(Terminated)

初始狀態(New)

線程對象被建立出來,即是初始狀態,這時候線程對象只是一個普通的對象,並非一個線程。

Runable

就緒狀態(Ready):執行start方法以後,進入就緒狀態,等待被分配到時間片。

運行狀態(Running):拿到CPU的線程開始執行。處於運行時間的線程並非永久的持有CPU直到運行結束,極可能沒有執行完畢時間片到期,就被收回CPU的使用權了,以後將會處於等待狀態。

等待狀態(Waiting)

等待狀態分爲有限期等待和無限期等待,所謂有限期等待是線程使用sleep方法主動進入休眠,有必定的時間限制,時間到期就從新進入就緒狀態,再次等待被CPU選中。

而無限期等待就有些不一樣了,無限期並非指永遠的等待下去,而是指沒有時間限制,可能等待一秒也可能不少秒。至於進入等待的緣由也不盡相同,多是由於CPU時間片到期,也多是由於一個比較耗時的操做(數據庫),或者主動的調用join方法。

wait和sleep的區別

wait sleep
wait()方法是Object類裏的方法 sleep()是Thread類的static(靜態)的方法
wait()睡眠時,釋放對象鎖 sleep()睡眠時,保持對象鎖,仍然佔有該鎖
經常使用於線程間通訊 經常使用於暫停執行
wait和notify/notifyAll是成對出現的, 必須在synchronize塊中被調用
阻塞狀態(Blocked)

在我看來,阻塞狀態其實是一種比較特殊的等待狀態,處於其餘等待狀態的線程是在等着別的線程執行結束,等着拿CPU的使用權;而處於阻塞狀態的線程等待的不只僅是CPU的使用權,主要是鎖標記,沒有拿到鎖標記,即使是CPU有空也沒有辦法執行。(關於鎖見下節:線程同步)

等待和阻塞的區別

等待 阻塞
已經拿到鎖對象,或者說不存在拿不到執行不了的狀況 等待拿到鎖對象
等待被喚醒 等待拿到鎖對象
終止線程(Terminated)

已經終止的線程會處於該種狀態。

總結

整體上來講,做爲一個線程挺倒黴的,首先,不會知道本身何時被選中;其次在執行過程當中隨時可能被打斷讓出CPU,最後碰到數據庫等耗時的操做也要讓出CPU去等待,而且就算數據準備好了, 仍然須要等着被挑選。

相關文章
相關標籤/搜索