在我們接觸編程時,就開始接觸各種生命周期,比如對象的生命周期,程序的生命周期等等,對於線程來說也是存在自己的生命周期,而且這也是面試與我們深入了解多線程必備的知識,今天我們主要介紹線程的生命周期及其各種狀態的轉換。
線程的六種狀態
線程的生命周期主要有以下六種狀態:
- 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