Java 線程生命週期

關注公衆號 JavaStorm編程

在操做系統層面,線程也有 【生命週期】,這是併發編程的基礎咱們須要掌握其中生命週期中各個節點的狀態轉換機制以及持有鎖狀態。文本將會介紹系統的週期以及在 Java編程語言的生命週期區別。打通併發編程任督二脈須要將基本心法緊緊掌握。多線程

通用的生命週期

能夠用以下圖所示的 「五態模式」 來描述,分別爲:初始狀態、可運行狀態、運行狀態、休眠狀態、終止狀態。併發

通用生命週期

  • 初始狀態:線程被建立,可是還不容許分配 CPU 執行,屬於編程語言特有,僅僅是在編程語言建立,操做系統層面尚未。
  • 可運行狀態:能夠分配 CPU 執行,操做系統也建立了線程。
  • 運行狀態:可運行狀態的線程獲取到 CPU 時間分片就轉換成了運行狀態。
  • 休眠狀態:運行狀態的 線程調用了一個阻塞的 API 或者等待某個事件,就會轉換成休眠狀態,同時會釋放 CPU 使用權,當等待的事件出現了,線程就會從休眠狀態轉換爲可運行狀態。
  • 終止狀態:線程執行完成或者異常則進入終止狀態,也就意味着線程的生命週期結束了。

Java 中的線程生命週期

接下來咱們着重看看 Java 中的生命週期,一共有 6 種狀態,分別是:編程語言

  • 新建 (New) :當程序使用 new 關鍵字建立了一個線程後。此時由 JVM 爲其分配內存,並初始化其成員變量的值。
  • 可運行/就緒(Runnable):當調用了 start()方法,線程就處於可運行狀態。Java 虛擬機會爲其建立方法調用棧可程序計數器,等到調度運行。
  • 運行狀態(Running):處於可運行狀態的線程得到 CPU 分片後,執行 run()方法。
  • 阻塞(Blocked):當處於運行狀態的線程失去了所佔有的資源。
  • 死亡(Dead):程序正常完成、或者線程拋出一個未捕獲的 Exception 貨或 Error。或者調用線程的中斷方法。

不要試圖對一個已經死亡的線程調用 start() 方法讓它從新啓動,死亡就是死亡了,該線程不可再次做爲線程執行。學習

線程狀態轉換以下圖所示:操作系統

狀態轉換

新建、可運行狀態

當使用 new 關鍵字建立一個線程後,僅僅由Java虛擬機爲其分配內存,並初始化其成員變量的值。此時的線程對象沒有表現出任何線程的動態特徵,程序也不會執行線程的線程執行體。線程

當線程對象調用了start()方法以後,該線程處於就緒狀態。Java 虛擬機會爲其建立方法調用棧和程序計數器,處於這個狀態中的線程並無開始運行,只是表示該線程能夠運行了。至於該線程什麼時候開始運行,取決於 JVM 裏線程調度器的調度。調試

注意:啓動線程使用 start() 方法,而不是 run() 方法。調用 start()方法啓動線程,系統會把該 run方法當成方法執行體處理。須要切記的是:調用了線程的 run()方法以後,該線程就不在處於新建狀態,不要再次調用 start()方法,只能對新建狀態的線程調用start()方法,不然會引起 IllegaIThreadStateExccption異常。日誌

1. RUNNABLE 與 BLOCKED 的狀態轉換

線程等待 synchronized 的隱式鎖,synchronized 修飾的方法、代碼塊同一時刻只容許一個線程執行,其餘線程只能等待,一個茅坑多我的想拉屎,只容許一我的在裏面其餘人只能在門外等着。這時候 等待的線程就會從 RUNNABLE 轉換到 BLOCKED 狀態。當某個等待的線程得到 synchronized 隱式鎖時,就會從 BLOCKED 轉換到 RUNNABLE 狀態。由於拿到去茅坑的小票了。code

2.RUNNABLE 與 WAITING 的狀態轉換

一共有三種場景觸發這種狀態轉換。

  • 得到 synchronized 鎖的線程調用 Object.wait()方法。會釋放鎖,而且放棄 CPU 分片調度。
  • 調用 Thread.join()方法。假如一個線程對象 thread A, 調用 A.join(),執行這條語句的線程會等待 thread A 執行完,而做爲等待的這個線程就會從 運行中變成 WAITING。好比主線程等待 thread A執行完成,那麼主線程就會變成等待狀態。
  • 調用 LockSupport.park()方法。Java 併發包的鎖都是基於此實現,調用 LockSupport.park()方法,釋放鎖。當前線程會阻塞,線程的狀態會從 RUNNABLE 轉換到 WAITING。調用 LockSupport.unpark(Thread thread)可喚醒目標線程,目標線程競爭到鎖後狀態又會從 WAITING 狀態轉換到 RUNNABLE。

3.RUNNABLE 與 TIMED_WAITING 的狀態轉換

觸發場景以下所示:

  • 調用帶超時參數的 Thread.sleep(long millis),並不會釋放鎖。
  • 得到 synchronized 隱式鎖的線程,調用帶超時參數Object.wait(long timeout) 方法;會釋放鎖。
  • 調用帶超時參數Thread.join(long millis)方法;
  • 調用帶超時參數LockSupport.parkNanos(Object blocker, long deadline)方法;會釋放鎖。
  • 調用帶超時參數LockSupport.parkUntil(long deadline)方法。會釋放鎖。

其實更多的都是多了超時參數。

4.從運行中到終止狀態

線程執行完run()方法後,會自動轉換到 終止狀態,可是當運行 run()方法異常的時候,也會致使線程終止,有時候咱們須要中斷 run()方法的執行,好比有的人佔着茅坑好久不拉屎,咱們要轟出去,後面的人等不了了,快憋死了。在 Java 中 Thread 類裏面卻是有個 stop()方法,不過已經標記爲 @Deprecated,因此不建議使用了。正確的姿式實際上是調用 interrupt()方法。

那 stop() 和 interrupt() 方法的主要區別是什麼呢?

stop() 方法會真的殺死線程,不給線程喘息的機會,若是線程持有 synchronized 隱式鎖,也不會釋放,那其餘線程就再也沒機會得到 synchronized 隱式鎖,這實在是太危險了。因此該方法就不建議使用了,相似的方法還有 suspend() 和 resume() 方法,這兩個方法一樣也都不建議使用了,因此這裏也就很少介紹了。

而 interrupt() 方法就溫柔多了,interrupt() 方法僅僅是通知線程,線程有機會執行一些後續操做,同時也能夠無視這個通知。被 interrupt 的線程,是怎麼收到通知的呢?一種是異常,另外一種是主動檢測。

總結

理解 Java 線程的各類狀態以及生命週期對於診斷多線程 Bug 很是有幫助,多線程程序很難調試,出了 Bug 基本上都是靠日誌,靠線程 dump 來跟蹤問題,分析線程 dump 的一個基本功就是分析線程狀態,大部分的死鎖、飢餓、活鎖問題都須要跟蹤分析線程的狀態。同時,本文介紹的線程生命週期具有很強的通用性,對於學習其餘語言的多線程編程也有很大的幫助。

你能夠經過 jstack

命令或者Java VisualVM

JavaStorm
相關文章
相關標籤/搜索