線程 6 種狀態之間的轉換——深度解析!

線程的 6 種狀態

就像生物從出生到長大、最終死亡的過程一樣,線程也有自己的生命週期,在 Java 中線程的生命週期中一共有 6 種狀態。

  New(新創建)

  Runnable(可運行)

  Blocked(被阻塞)

  Waiting(等待)

  Timed Waiting(計時等待)

  Terminated(被終止)

如果想要確定線程當前的狀態,可以通過 getState() 方法,並且線程在任何時刻只可能處於 1 種狀態。

 

 New 新創建         


  下面我們逐個介紹線程的 6 種狀態,如圖所示,首先來看下左上角的 New 狀態。

    

      New 表示線程被創建但尚未啓動的狀態:當我們用 new Thread() 新建一個線程時,如果線程沒有開始運行 start() 方法,所以也沒有開始執行 run() 方法裏面的代碼,那麼此時它的狀態就是 New。而一旦線程調用了 start(),它的狀態就會從 New 變成 Runnable,也就是狀態轉換圖中中間的這個大方框裏的內容。

Runnable 可運行

    Java 中的 Runable 狀態對應操作系統線程狀態中的兩種狀態,分別是 Running 和 Ready,也就是說,Java 中處於 Runnable 狀態的線程有可能正在執行,也有可能沒有正在執行,正在等待被分配 CPU 資源。

    所以,如果一個正在運行的線程是 Runnable 狀態,當它運行到任務的一半時,執行該線程的 CPU 被調度去做其他事情,導致該線程暫時不運行,它的狀態依然不變,還是 Runnable,因爲它有可能隨時被調度回來繼續執行任務。

阻塞狀態

     接下來,我們來看下 Runnable 下面的三個方框,它們統稱爲阻塞狀態,在 Java 中阻塞狀態通常不僅僅是 Blocked,實際上它包括三種狀態,分別是 Blocked(被阻塞)、Waiting(等待)、Timed Waiting(計時等待),這三 種狀態統稱爲阻塞狀態,下面我們來看看這三種狀態具體是什麼含義

Blocked 被阻塞

 

      首先來看最簡單的 Blocked,從箭頭的流轉方向可以看出,從 Runnable 狀態進入 Blocked 狀態只有一種可能,就是進入 synchronized 保護的代碼時沒有搶到 monitor 鎖,無論是進入 synchronized 代碼塊,還是 synchronized 方法,都是一樣。

我們再往右看,當處於 Blocked 的線程搶到 monitor 鎖,就會從 Blocked 狀態回到Runnable 狀態。

Waiting 等待

  我們再看看 Waiting 狀態,線程進入 Waiting 狀態有三種可能性。

  沒有設置 Timeout 參數的 Object.wait() 方法。

  沒有設置 Timeout 參數的 Thread.join() 方法。

  LockSupport.park() 方法。

   剛纔強調過,Blocked 僅僅針對 synchronized monitor 鎖,可是在 Java 中還有很多其他的鎖,比如 ReentrantLock,如果線程在獲取這種鎖時沒有搶到該鎖就會進入 Waiting 狀態,因爲本質上它執行了 LockSupport.park() 方法,所以會進入 Waiting 狀態。同樣,Object.wait() 和 Thread.join() 也會讓線程進入 Waiting 狀態。

   Blocked 與 Waiting 的區別是 Blocked 在等待其他線程釋放 monitor 鎖,而 Waiting 則是在等待某個條件,比如 join 的線程執行完畢,或者是 notify()/notifyAll() 。

Timed Waiting 限期等待

     在 Waiting 上面是 Timed Waiting 狀態,這兩個狀態是非常相似的,區別僅在於有沒有時間限制,Timed Waiting 會等待超時,由系統自動喚醒,或者在超時前被喚醒信號喚醒。

以下情況會讓線程進入 Timed Waiting 狀態。

    設置了時間參數的 Thread.sleep(long millis) 方法;

    設置了時間參數的 Object.wait(long timeout) 方法;

    設置了時間參數的 Thread.join(long millis) 方法;

    設置了時間參數的 LockSupport.parkNanos(long nanos) 方法和 LockSupport.parkUntil(long deadline) 方法。

講完如何進入這三種狀態,我們再來看下如何從這三種狀態流轉到下一個狀態

     想要從 Blocked 狀態進入 Runnable 狀態,要求線程獲取 monitor 鎖,而從 Waiting 狀態流轉到其他狀態則比較特殊,因爲首先 Waiting 是不限時的,也就是說無論過了多長時間它都不會主動恢復。

  只有當執行了 LockSupport.unpark(),或者 join 的線程運行結束,或者被中斷時纔可以進入 Runnable 狀態。

     如果其他線程調用 notify() 或 notifyAll()來喚醒它,它會直接進入 Blocked 狀態,這是爲什麼呢?因爲喚醒 Waiting 線程的線程如果調用 notify() 或 notifyAll(),要求必須首先持有該 monitor 鎖,所以處於 Waiting 狀態的線程被喚醒時拿不到該鎖,就會進入 Blocked 狀態,直到執行了 notify()/notifyAll() 的喚醒它的線程執行完畢並釋放 monitor 鎖,纔可能輪到它去搶奪這把鎖,如果它能搶到,就會從 Blocked 狀態回到 Runnable 狀態。

    同樣在 Timed Waiting 中執行 notify() 和 notifyAll() 也是一樣的道理,它們會先進入 Blocked 狀態,然後搶奪鎖成功後,再回到 Runnable 狀態。

    當然對於 Timed Waiting 而言,如果它的超時時間到了且能直接獲取到鎖/join的線程運行結束/被中斷/調用了LockSupport.unpark(),會直接恢復到 Runnable 狀態,而無需經歷 Blocked 狀態。

Terminated 終止

再來看看最後一種狀態,Terminated 終止狀態,要想進入這個狀態有兩種可能。

  run() 方法執行完畢,線程正常退出。

  出現一個沒有捕獲的異常,終止了 run() 方法,最終導致意外終止。

注意點:

     1.線程的狀態是需要按照箭頭方向來走的,比如線程從 New 狀態是不可以直接進入 Blocked 狀態的,它需要先經歷 Runnable 狀態。

    2. 線程生命週期不可逆:一旦進入 Runnable 狀態就不能回到 New 狀態;一旦被終止就不可能再有任何狀態的變化。所以一個線程只能有一次 New 和 Terminated 狀態,只有處於中間狀態纔可以相互轉換。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章