Java給多線程編程提供了內置的支持。一條線程指的是進程中一個單一順序的控制流,一個進程中可以併發多個線程,每條線程並行執行不同的任務。
多線程是多任務的一種特別的形式,但多線程使用了更小的資源開銷。
這裏定義和線程相關的另一個術語—進程:一個進程包括由操作系統分配的內存空間,包含一個或者多個線程。一個線程不能獨立的存在,它必須是進程的一部分。一個進程一直運行,直到所有的非守護線程都結束運行後才能結束。
多線程能滿足程序員編寫高效率的程序來達到充分利用CPU的目的。
public enum State {
//線程被new出來,還沒開始運行就是這狀態,對應枚舉下標0
NEW,
//線程調用start()方法後就會處在這個狀態,不管有沒有CPU資源分配給它,對應枚舉下標1
RUNNABLE,
//線程等待獲取鎖的時候就是這個狀態,對應枚舉下標2
BLOCKED,
//調用了wait(),join()等方法,卻沒有時間限制,除非有其他線程喚醒或者是中斷,否則線程就處於這個狀態,對應枚舉下標3
WAITING,
//調用了sleep(),wait(),join()等方法,不過設置了時間限制,所以會在指定的時間後自行返回,在這段時間裏線程處於這個狀態,對應枚舉下標4
TIMED_WAITING,
//線程執行完畢,就會變成這狀態,對應枚舉下標爲5
TERMINATED;
}
一個線程的生命週期
線程是一個動態執行的過程,它也有一個從產生到死亡的過程。
下圖顯示了一個線程完整的生命週期。
對於進程來說,從運行狀態轉向就緒狀態(時間片用完)。
- 新建狀態:
使用new關鍵字和Thread類及其子類建立一個線程對象後,該對象就處於新建狀態。它保持這個狀態直到程序start()這個線程。
- 就緒狀態:
當線程對象調用了start()方法之後,該線程就處於就緒狀態。就緒狀態的線程處於就緒隊列中,要等待JVM裏線程調度器的調度。
- 運行狀態:
如果就緒狀態的線程獲取CPU資源,就可以執行run(),此時線程便處於運行狀態。處於運行狀態的線程最爲複雜,它可以變爲阻塞狀態、就緒狀態和死亡狀態(完成狀態)。
- 阻塞狀態:
如果一個線程執行了sleep(睡眠)、suspend(掛起)等方法,失去所佔用資源之後,該線程就從運行狀態進入阻塞狀態。在睡眠時間已到或者獲得設備資源之後可以重新進入就緒狀態。可以分爲三種:
- 等待阻塞:運行狀態中的線程執行wait()方法,使線程進入到等待阻塞狀態。
- 同步阻塞:線程在獲取synchronized同步鎖失敗(因爲同步鎖被其他線程佔用)。
- 其他阻塞:通過調用線程的sleep()或者join()發出I/O請求時,線程就會進入到阻塞狀態。當sleep()狀態超時,join()等待線程終止或者超時,或者I/O處理完畢,線程重新轉入就緒狀態。
- 死亡狀態:
一個運行狀態的線程完成任務或者其他終止條件發生時,該線程就會切換到終止狀態。
線程的優先級
每一個Java線程都有一個優先級,這樣有助於操作系統確定線程的調度順序。
Java線程的優先級是一個整數,其取值範圍是1(Thread.MIN_PRIORITY)-10(Thread.MAX_PRIORITY)。
默認情況下,每一個線程都會分配一個優先級NORM_PRIORITY(5)。
具有較高優先級的線程對程序更重要,並且應該在低優先級的線程之前分配處理器資源。但是,線程優先級不能保證線程執行的順序,而且非常依賴於平臺。
創建一個線程
Java提供了三種創建線程的方法:
- 通過實現Runnable接口;
- 通過繼承Thread類本身;
- 通過Callable和Future創建線程。
通過實現Runnable接口來創建線程
創建一個線程,最簡單的方法是創建一個實現Runnable接口的類。
爲了實現Runnable,一個類只需要執行一個方法調用run(),聲明如下:
1 |
|
你可以重寫該方法,重要的是理解run()可以調用其他方法,使用其他類,並聲明變量,就像主線程一樣。
在創建一個實現Runnable接口的類之後,你可以在類中實例化一個線程對象。
Thread定義了幾個構造方法,下面的這個是我們經常使用的:
1 |
|
這裏,threadOb是一個實現Runnable接口的類的實例,並且threadName指定新線程的名字。
新線程創建之後,你調用它的start()方法它纔會運行。
1 |
|
通過繼承Thread來創建線程
創建一個線程的第二種方法是創建一個新的類,該類繼承Thread類,然後創建一個該類的實例。
繼承類必須重寫run()方法,該方法是新線程的入口點。它也必須調用start()方法才能執行。
該方法儘管被列爲一種多線程實現方式,但是本質上也是實現了Runnable接口的一個實例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
|
Thread方法
下表列出了Thread類的一些重要方法:
測試線程是否處於活動狀態。上述方法是被Thread對象調用的。下面的方法是Thread類的靜態方法。
實例
如下的ThreadClassDemo程序演示了Thread類的一些方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
|
通過Callable和Future創建線程
- 創建Callable接口的實現類,並實現call()方法,該call()方法將作爲線程執行體,並且有返回值。
- 創建Callable實現類的實例,使用FutureTask類來包裝Callable對象,該FutureTask對象封裝了該Callable對象的call()方法的返回值。
- 使用FutureTask對象作爲Thread對象的target創建並啓動新線程。
- 調用FutrueTask對象的get()方法來獲得子線程執行結束後的返回值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
|
創建線程的三種方式的對比
採用實現Runnable、Callable接口的方式創建多線程時,線程類只是實現了Runnable接口或者Callable接口,還可以繼承其他類。
使用繼承Thread類的方式創建多線程時,編寫簡單,如果需要訪問當前線程,則無需使用Thread.currentThread()方法,直接使用this即可獲得當前線程。
線程的幾個主要概念
- 線程同步
- 線程間通信
- 線程死鎖
- 線程控制:掛起、停止和恢復
多線程的使用
有效利用多線程的關鍵是理解程序是併發執行而不是串行執行的,例如:程序中有兩個子程序需要併發執行,這時候就需要利用多線程編程。
通過對多線程的使用,可以編寫出非常高效的程序。不過請注意,如果你創建太多的線程,程序執行的效率實際上是降低了,而不是提升了。
請記住,上下文的切換開銷也很重要,如果你創建了太多的線程,CPU花費在上下文的切換的時間將多於執行程序的時間。