在我們接觸程式設計時,就開始接觸各種生命週期,比如物件的生命週期,程式的生命週期等等,對於執行緒來說也是存在自己的生命週期,而且這也是面試與我們深入瞭解多執行緒必備的知識,今天我們主要介紹執行緒的生命週期及其各種狀態的轉換。
執行緒的六種狀態
執行緒的生命週期主要有以下六種狀態:
- New(新建立)
- Runnable(可執行)
- Blocked(被阻塞)
- Waiting(等待)
- Timed Waiting(計時等待)
- Terminated(被終止)
在我們程式編碼中如果想要確定執行緒當前的狀態,可以通過getState()
方法來獲取,同時我們需要注意任何執行緒在任何時刻都只能是處於一種狀態。
New 新建狀態
- 首先我們展示一下整個執行緒狀態的轉換流程圖,下面我們將進行詳細的介紹講解,如下圖所示,我們可以直觀的看到六種狀態的轉換,首先左側上方是
NEW
狀態,這是建立新執行緒的狀態,相當於我們new Thread()
的過程。
New
表示執行緒被建立但尚未啟動的狀態:當我們用new Thread()
新建一個執行緒時,如果執行緒沒有開始執行start()
方法,那麼執行緒也就沒有開始執行run()
方法裡面的程式碼,那麼此時它的狀態就是New
。而一旦執行緒呼叫了start()
,它的狀態就會從New
變成Runnable
,進入到圖中綠色的方框
Runnable 可執行狀態
-
Java
中的**Runable **
狀態對應作業系統執行緒狀態中的兩種狀態,分別是Running
和Ready
,也就是說,Java
中處於Runnable
狀態的執行緒有可能正在執行,也有可能沒有正在執行,正在等待被分配 CPU 資源。 -
所以,如果一個正在執行的執行緒是
Runnable
狀態,當它執行到任務的一半時,執行該執行緒的CPU
被排程去做其他事情,導致該執行緒暫時不執行,它的狀態依然不變,還是Runnable
,因為它有可能隨時被排程回來繼續執行任務。
**阻塞狀態**
- 上面認識了執行緒的關鍵狀態
Runnable
,那麼接下來我們來看一下下面的三個狀態,這三個狀態我們可以統稱為阻塞狀態,它們分別是Blocked(被阻塞)
、Waiting(等待)
、Timed Waiting(計時等待)
.
Blocked 被阻塞狀態
- 首先我們來認識一下
Blocked
狀態,這是一個相對簡單的狀態,我們可以通過下面的圖示看到,從Runnable
狀態進入到Blocked
狀態只有一種途徑,那麼就是當進入到synchronized
程式碼塊中時未能獲得相應的monitor
鎖(關於monitor
鎖我們在之後專門來介紹,這裡我們知道synchronized
的實現都是基於monitor
鎖的),
- 在右側我們可以看到,有連線線從
Blocked
狀態指向了Runnable
,也只有一種情況,那麼就是當執行緒獲得monitor
鎖,此時執行緒就會進入Runnable
狀體中參與CPU
資源的搶奪
Waiting 等待狀態
上面我們看完阻塞狀態,那麼接下來我們瞭解一下 Waiting
狀態,對於 Waiting
狀態的進入有三種情況,如下圖中所示,分別為:
- 當執行緒中呼叫了沒有設定
Timeout
引數的Object.wait()
方法 - 當執行緒呼叫了沒有設定
Timeout
引數的Thread.join()
方法 - 當執行緒呼叫了
LockSupport.park()
方法
關於
LockSupport.park()
方法,這裡說一下,我們通過上面知道Blocked
是針對synchronized monitor
鎖的,但是在Java
中實際是有很多其他鎖的,比如ReentrantLock
等,在這些鎖中,如果執行緒沒有獲取到鎖則會直接進入Waiting
狀態,其實這種本質上它就是執行了LockSupport.park()
方法進入了Waiting
狀態
**Blocked **
與**Waiting**
的區別Blocked
是在等待其他執行緒釋放monitor
鎖Waiting
則是在等待某個條件,比如join
的執行緒執行完畢,或者是notify()/notifyAll()
。
Timed Waiting 計時等待狀態
- 最後我們來說說這個
Timed Waiting
狀態,它與Waiting
狀態非常相似,其中的區別只在於是否有時間的限制,在Timed Waiting
狀態時會等待超時,之後由系統喚醒,或者也可以提前被通知喚醒如notify
通過上述圖我們可以看到在以下情況會讓執行緒進入 Timed Waiting
狀態。
- 執行緒執行了設定了時間引數的
Thread.sleep(long millis)
方法; - 執行緒執行了設定了時間引數的
Object.wait(long timeout)
方法; - 執行緒執行了設定了時間引數的
Thread.join(long millis)
方法; - 執行緒執行了設定了時間引數的
LockSupport.parkNanos(long nanos)
方法和LockSupport.parkUntil(long deadline)
方法。
通過這個我們可以進一步看到它與 waiting 狀態的相同
執行緒狀態間轉換
上面我們講了各自狀態的特點和執行狀態進入相應狀態的情況 ,那麼接下來我們將來分析各自狀態之間的轉換,其實主要就是 Blocked
、waiting
、Timed Waiting
三種狀態的轉換 ,以及他們是如何進入下一狀態最終進入 Runnable
Blocked
進入 Runnable
- 想要從
Blocked
狀態進入Runnable
狀態,我們上面說過必須要執行緒獲得monitor
鎖,但是如果想進入其他狀態那麼就相對比較特殊,因為它是沒有超時機制的,也就是不會主動進入。
如下圖中紫色加粗表示線路:
Waiting
進入 Runnable
- 只有當執行了
LockSupport.unpark()
,或者join
的執行緒執行結束,或者被中斷時才可以進入Runnable
狀態。 - 如下圖示註
- 如果通過其他執行緒呼叫
notify()
或notifyAll()
來喚醒它,則它會直接進入Blocked
狀態,這裡大家可能會有疑問,不是應該直接進入Runnable
嗎?這裡需要注意一點 ,因為喚醒Waiting
執行緒的執行緒如果呼叫notify()
或notifyAll()
,要求必須首先持有該monitor
鎖,這也就是我們說的wait()
、notify
必須在synchronized
程式碼塊中。 - 所以處於
Waiting
狀態的執行緒被喚醒時拿不到該鎖,就會進入Blocked
狀態,直到執行了notify()/notifyAll()
的喚醒它的執行緒執行完畢並釋放monitor
鎖,才可能輪到它去搶奪這把鎖,如果它能搶到,就會從Blocked
狀態回到Runnable
狀態。
這裡大家一定要注意這點,當我們通過 notify 喚醒時,是先進入阻塞狀態的 ,再等搶奪到 monitor 鎖喉才會進入 Runnable 狀態!
**`Timed Waiting` 進入 `Runnable`**
- 同樣在
Timed Waiting
中執行notify()
和notifyAll()
也是一樣的道理,它們會先進入Blocked
狀態,然後搶奪鎖成功後,再回到Runnable
狀態。
- 但是對於 Timed Waiting 而言,它存在超時機制,也就是說如果超時時間到了那麼就會系統自動直接拿到鎖,或者當
join
的執行緒執行結束/呼叫了LockSupport.unpark()
/被中斷等情況都會直接進入Runnable
狀態,而不會經歷Blocked
狀態
Terminated 終止
最後我們來說最後一種狀態,Terminated
終止狀態,要想進入這個狀態有兩種可能。
- run() 方法執行完畢,執行緒正常退出。
- 出現一個沒有捕獲的異常,終止了 run() 方法,最終導致意外終止。
總結
最後我們說一下再看執行緒轉換的過程中一定要注意兩點:
-
執行緒的狀態是按照箭頭方向來走的,比如執行緒從
New
狀態是不可以直接進入Blocked
狀態的,它需要先經歷Runnable
狀態。 -
執行緒生命週期不可逆:一旦進入
Runnable
狀態就不能回到New
狀態;一旦被終止就不可能再有任何狀態的變化。 -
所以一個執行緒只能有一次
New
和Terminated
狀態,只有處於中間狀態才可以相互轉換。也就是這兩個狀態不會參與相互轉化
本文由AnonyStar 釋出,可轉載但需宣告原文出處。
歡迎關注微信公賬號 :雲棲簡碼 獲取更多優質文章
更多文章關注筆者部落格 :雲棲簡碼 i-code.online