Java線程的生命周期
New(新建立)
Runnable(可運作)
Blocked(被阻塞)
Waiting(等待)
Timed Waiting(計時等待)
Terminated(被終止)
在我們程式編碼中如果想要确定線程目前的狀态,可以通過<code>getState()</code>方法來擷取,同時我們需要注意任何線程在任何時刻都隻能是處于一種狀态。
java api java.lang.Thread.State 這個枚舉中給出了六種線程狀态,分别是:
<col>
線程狀态
導緻狀态發生條件
NEW(建立)
線程剛被建立,但是并未啟動。 還沒調用start方法 。
Runnable(可運作)
Java線程中将就緒(ready)和運作中(running)兩種狀态籠統的稱為“運作”。
ready: 線程**等待被線程排程器選中,得到時間片(擷取cpu的執行權 ) **就能在cpu上面執行
running: 就緒狀态的線程在獲得CPU時間片後變為運作中狀态(running)
是否擷取到時間片就是這兩者狀态的差別
阻塞(BLOCKED):
受阻塞并等待某個螢幕鎖的線程處于這種狀态
等待(WAITING):
進入該狀态的線程需要等待其他線程做出一些特定動作(通知或中斷)
逾時等待(TIMED_WAITING):
該狀态不同于WAITING,它可以在指定的時間後自行傳回。
. 終止(TERMINATED):
表示該線程已經執行完畢。
一、線程的狀态圖

二、狀态詳細說明
1.初始狀态(NEW)
<code>New</code>表示線程被建立但尚未啟動的狀态:當我們用 <code>new Thread()</code>建立一個線程時,如果線程沒有開始運作 <code>start()</code> 方法,那麼線程也就沒有開始執行 <code>run()</code>方法裡面的代碼,那麼此時它的狀态就是 <code>New</code>。而一旦線程調用了 <code>start()</code>,它的狀态就會從 <code>New</code>變成 <code>Runnable</code>,進入到圖中綠色的方框
2.可運作狀态(RUNNABLE)
<code>Runnable</code> 狀态的線程有可能正在執行,
也有可能沒有執行,正在等待被配置設定 CPU 資源。
2.1就緒狀态(RUNNABLE之READY)
調用線程的start()方法,此線程進入就緒狀态。
就緒狀态隻是說你資格運作,排程程式沒有挑選到你,你就永遠是就緒狀态。
目前線程sleep()方法結束,其他線程join()結束,等待使用者輸入完畢,這些線程也将進入就緒狀态。
目前線程時間片用完了,調用目前線程的yield()方法,目前線程進入就緒狀态。
鎖池裡的線程拿到對象鎖後,進入就緒狀态。
2.2. 運作中狀态(RUNNABLE之RUNNING)
就緒狀态和運作狀态可以互相轉換,比如 正在執行的線程時間片沒了,此時排程器把cpu的時間片給了其他線程。
正在執行的線程也可以自動調用yield方法主動放棄時間片,将cpu的執行權給别的線程
3.阻塞狀态(BLOCKED)
阻塞狀态是線程阻塞在進入synchronized關鍵字修飾的方法或代碼塊(未擷取monitor鎖)時的狀态。
我們可以通過下面的圖示看到,從 <code>Runnable</code> 狀态進入到 <code>Blocked</code> 狀态隻有一種途徑,那麼就是當進入到 <code>synchronized</code> 代碼塊中時未能獲得相應的 <code>monitor</code> 鎖(關于 <code>monitor</code> 鎖我們在之後專門來介紹,這裡我們知道 <code>synchronized</code> 的實作都是基于 <code>monitor</code> 鎖的)
在右側我們可以看到,有連接配接線從 <code>Blocked</code> 狀态指向了 <code>Runnable</code> ,也隻有一種情況,那麼就是當線程獲得 <code>monitor</code> 鎖,此時線程就會進入 <code>Runnable</code> 狀體中參與 <code>CPU</code> 資源的搶奪
4.等待(WAITING)
對于 <code>Waiting</code> 狀态的進入有三種情況,如下圖中所示,分别為:
當線程中調用了沒有設定 <code>Timeout</code> 參數的 <code>Object.wait()</code> 方法
當線程調用了沒有設定 <code>Timeout</code> 參數的 <code>Thread.join()</code> 方法
當線程調用了 <code>LockSupport.park()</code> 方法
關于 <code>LockSupport.park()</code> 方法,這裡說一下,我們通過上面知道 <code>Blocked</code> 是針對 <code>synchronized monitor</code> 鎖的, 但是在 <code>Java</code> 中實際是有很多其他鎖的: 比如 <code>ReentrantLock</code> 等,在這些鎖中,如果線程沒有擷取到鎖則會直接進入 <code>Waiting</code> 狀态,其實這種本質上它就是執行了 <code>LockSupport.park()</code> 方法進入了<code>Waiting</code> 狀态
<code>**Blocked **</code>與 **<code>**Waiting**</code> 的差別**
<code>Blocked</code> 是在等待其他線程釋放 <code>monitor</code> 鎖 ,其他條件都滿足了吧
<code>Waiting</code> 則是在等待某個條件,比如 <code>join</code> 的線程執行完畢,或者是 <code>notify()/notifyAll()</code>。
5.逾時等待(TIMED_WAITING)
最後我們來說說這個 <code>Timed Waiting</code> 狀态,它與 <code>Waiting</code> 狀态非常相似,其中的差別隻在于是否有時間的限制,在 <code>Timed Waiting</code> 狀态時會等待逾時,之後由系統喚醒,或者也可以提前被通知喚醒如 <code>notify</code>
處于這種狀态的線程不會被配置設定CPU執行時間,不過無須無限期等待被其他線程顯示地喚醒,在達到一定時間後它們會自動喚醒。通過上述圖我們可以看到在以下情況會讓線程進入 <code>Timed Waiting</code>狀态。
線程執行了設定了時間參數的<code>Thread.sleep(long millis)</code> 方法;
線程執行了設定了時間參數的 <code>Object.wait(long timeout)</code> 方法;
線程執行了設定了時間參數的 <code>Thread.join(long millis)</code> 方法;
線程執行了設定了時間參數的 <code>LockSupport.parkNanos(long nanos)</code> 方法和 <code>LockSupport.parkUntil(long deadline)</code> 方法。
通過這個我們可以進一步看到它與 waiting 狀态的相同
6.終止狀态(TERMINATED)
run() 方法執行完畢,線程正常退出。
出現一個沒有捕獲的異常,終止了 run() 方法,最終導緻意外終止。
在一個終止的線程上調用start()方法,會抛出java.lang.IllegalThreadStateException異常
二:線程狀态間轉換
上面我們講了各自狀态的特點和運作狀态進入相應狀态的情況 ,那麼接下來我們将來分析各自狀态之間的轉換,其實主要就是 <code>Blocked</code>、<code>waiting</code>、<code>Timed Waiting</code> 三種狀态的轉換 ,以及他們是如何進入下一狀态最終進入 <code>Runnable</code>
想要從 <code>Blocked</code> 狀态進入<code>Runnable</code> 狀态,我們上面說過必須要線程獲得 <code>monitor</code> 鎖,但是如果想進入其他狀态那麼就相對比較特殊,因為它是沒有逾時機制的,也就是不會主動進入。
如下圖中紫色加粗表示線路:
隻有當執行了<code>LockSupport.unpark()</code>,或者<code>join</code> 的線程運作結束,或者被中斷時才可以進入 <code>Runnable</code> 狀态。
如下圖示注
如果通過其他線程調用 <code>notify()</code> 或 <code>notifyAll()</code>來喚醒它,則它會直接進入 <code>Blocked</code> 狀态,這裡大家可能會有疑問,不是應該直接進入 <code>Runnable</code> 嗎?這裡需要注意一點 ,因為喚醒 <code>Waiting</code> 線程的線程如果調用 <code>notify()</code> 或 <code>notifyAll()</code>,要求必須首先持有該 <code>monitor</code> 鎖,這也就是我們說的 <code>wait()</code>、<code>notify</code> 必須在 <code>synchronized</code> 代碼塊中。
是以處于 <code>Waiting</code> 狀态的線程被喚醒時拿不到該鎖,就會進入 <code>Blocked</code> 狀态,直到執行了 <code>notify()/notifyAll()</code> 的喚醒它的線程執行完畢并釋放 <code>monitor</code> 鎖,才可能輪到它去搶奪這把鎖,如果它能搶到,就會從 <code>Blocked</code> 狀态回到 <code>Runnable</code> 狀态。
同樣在 <code>Timed Waiting</code> 中執行 <code>notify()</code> 和 <code>notifyAll()</code>也是一樣的道理,它們會先進入 <code>Blocked</code> 狀态,然後搶奪鎖成功後,再回到 <code>Runnable</code> 狀态。
但是對于 Timed Waiting 而言,它存在逾時機制,也就是說如果逾時時間到了那麼就會系統自動直接拿到鎖,或者當 <code>join</code> 的線程執行結束/調用了<code>LockSupport.unpark()</code>/被中斷等情況都會直接進入 <code>Runnable</code> 狀态,而不會經曆 <code>Blocked</code> 狀态
三: 從mointorObject 的角度上了解阻塞與等待的差別
monitor可以把它了解為 一個同步工具,也可以描述為 一種同步機制,它通常被 描述為一個對象。
與一切皆對象一樣,所有的Java對象是天生的Monitor,每一個Java對象都有成為Monitor的潛質,因為在Java的設計中 ,每一個Java對象自打娘胎裡出來就帶了一把看不見的鎖,它叫做内部鎖或者Monitor鎖。也就是通常說Synchronized的對象鎖。
在Java虛拟機(HotSpot)中,Monitor是由ObjectMonitor實作的,其主要資料結構如下(位于HotSpot虛拟機源碼ObjectMonitor.hpp檔案,C++實作的):
ObjectMonitor中有兩個隊列,_WaitSet 和 _EntryList,用來儲存ObjectWaiter對象清單( 每個等待鎖的線程都會被封裝成ObjectWaiter對象 ),_owner指向持有ObjectMonitor對象的線程,當多個線程同時通路一段同步代碼時(即通路同一臨界區時)
首先這些線程會進入 _EntryList 集合(處于阻塞狀态的線程會放到該清單中),當線程A擷取到對象的monitor後,Monitor是依賴于底層作業系統的mutex lock來實作互斥的,線程擷取mutex成功,則會持有該mutex,這時其它線程就無法再擷取到該mutex。線程A進入 _Owner區域并把monitor中的owner變量設定為目前線程A,同時monitor中的計數器count加1;
若線程A調用 wait() 方法,釋放目前它鎖持有的mutex, owner變量恢複為null,count自減1,并且該線程會進入到WaitSet集合(等待集合)中,等待下一次被其他線程調用notify/notifyAll喚醒。;
若線程A執行完畢,也将釋放monitor(mutex鎖)并複位count的值,以便其他線程擷取monitor(mutex鎖)進入monitor;
總結一下:
那些處于EntrySet和WaitSet中的線程均處于阻塞狀态(OS級别我們叫做阻塞,Java不叫作阻塞 。。javascript:void(0) 這邊有詳解講解os線程狀态跟Java線程狀态的差別),
阻塞操作是由作業系統來完成的,線程被阻塞後便會進入到核心排程狀态。線程的阻塞和喚醒需要CPU從使用者态轉為核心态(也叫做核心陷入),頻繁的阻塞和喚醒對CPU來說是一件負擔很重的工作,勢必會給系統的并發性能帶來很大的壓力。(在linux下是通過pthread_mutex_lock函數實作的)
同時我們發現在許多應用上面,對象鎖的鎖狀态隻會持續很短一段時間,為了這一段很短的時間頻繁地阻塞和喚醒線程是非常不值得的。是以引入自旋鎖。
所謂自旋鎖,就是讓該線程等待一段時間,不會被立即挂起,看持有鎖的線程是否會很快釋放鎖。怎麼等待呢?執行一段無意義的循環即可(自旋)
即:當發生對Monitor的争用時,若owner線程能夠在很短的時間内釋放掉鎖,則那些正在争用的線程就可以稍微等待一下(既所謂的自旋),在Owner線程釋放鎖之後,争用線程可能會立刻擷取到鎖,進而避免了系統阻塞。
不過,當Owner運作的時間超過了臨界值後,争用線程自旋一段時間後依然無法擷取到鎖,這時争用線程則會停止自旋而進入到阻塞狀态。
是以總體的思想是:先自旋,不成功再進行阻塞,盡量降低阻塞的可能性,這對那些執行時間很短的代碼來說有極大的性能提升。顯然,自旋在多處理器(多核心)上才有意義 。
總結
最後我們說一下再看線程轉換的過程中一定要注意兩點:
線程的狀态是按照箭頭方向來走的,比如線程從 <code>New</code>狀态是不可以直接進入 <code>Blocked</code> 狀态的,它需要先經曆 <code>Runnable</code> 狀态。
線程生命周期不可逆:一旦進入 <code>Runnable</code> 狀态就不能回到 <code>New</code> 狀态;一旦被終止就不可能再有任何狀态的變化。
是以一個線程隻能有一次 <code>New</code>和 <code>Terminated</code>狀态,隻有處于中間狀态才可以互相轉換。也就是這兩個狀态不會參與互相轉化