總算把執行緒六種狀態的轉換說清楚了!

AnonyStar發表於2020-10-19






在我們接觸程式設計時,就開始接觸各種生命週期,比如物件的生命週期,程式的生命週期等等,對於執行緒來說也是存在自己的生命週期,而且這也是面試與我們深入瞭解多執行緒必備的知識,今天我們主要介紹執行緒的生命週期及其各種狀態的轉換。

執行緒的六種狀態

執行緒的生命週期主要有以下六種狀態:

  • 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() 方法

Waiting 等待狀態

關於 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 狀態的相同

執行緒狀態間轉換

上面我們講了各自狀態的特點和執行狀態進入相應狀態的情況 ,那麼接下來我們將來分析各自狀態之間的轉換,其實主要就是 BlockedwaitingTimed 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

相關文章