贊助平台
首頁 / 文章管理 / 文章編輯
Java線程狀态
友情提示:文章每30秒自動儲存一次,編輯器支援圖檔拖動上傳或者複制粘貼上傳~
0 線程狀态概述
分類
6個狀态定義: java.lang.Thread.State
- New: 尚未啟動的線程的線程狀态。
- Runnable: 可運作線程的線程狀态,等待CPU排程。
-
Blocked: 線程阻塞等待螢幕鎖定的線程狀态。
處于synchronized同步代碼塊或方法中被阻塞。
-
Waiting: 等待線程的線程狀态。下 列不帶逾時的方式:
Object.wait、Thread.join、 LockSupport.park
-
Timed Waiting:具有指定等待時間的等待線程的線程狀态。下 列帶逾時的方式:
Thread.sleep、0bject.wait、 Thread.join、 LockSupport.parkNanos、 LockSupport.parkUntil
-
Terminated: 終止線程的線程狀态。線程正常完成執行或者出現異常。
流程圖
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsICM38FdsYkRGZkRG9lcvx2bjxSMfVmepNHL3VEVOVzZU5keNpHW4Z0MMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnLwQjNyEDO0IDM2IDOwkTMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
1 NEW
實作Runnable接口和繼承Thread可以得到一個線程類,new一個執行個體出來,線程就進入了初始狀态
線程還是沒有開始執行
有狀态了,那肯定是已經建立好線程對象了(如果對象都沒有,何來狀态這說),
問題的焦點就在于還沒有開始執行,當調用線程的start()方法時,線程不一定會馬上執行,因為
Java線程是映射到作業系統的線程執行
,此時可能還需要等作業系統排程,但此時該線程的狀态已經為RUNNABLE
2 RUNNABLE
隻是說你有資格運作,排程程式沒有挑選到你,你就永遠是可運作狀态。
2.1條件
- 調用start(),進入可運作态
- 目前線程sleep()結束,其他線程join()結束,等待使用者輸入完畢,某個線程拿到對象鎖,這些線程也将進入可運作狀态
- 目前線程時間片用完,調用目前線程的yield()方法,目前線程進入可運作狀态
- 鎖池裡的線程拿到對象鎖後,進入可運作狀态
- 正在執行線程必屬于此态
這個狀态是最有争議的,注釋中說了,它表示線程在JVM層面是執行的,但在作業系統層面不一定,它舉例是CPU,毫無疑問CPU是一個作業系統資源,但這也就意味着在等作業系統其他資源的時候,線程也會是這個狀态
這裡就有一個關鍵點IO阻塞算是等作業系統的資源?
3 BLOCKED
被挂起,線程因為某種原因放棄了cpu timeslice,暫時停止運作。
3.1條件
- 目前線程調用Thread.sleep(),進入阻塞态
- 運作在目前線程裡的其它線程調用join(),目前線程進入阻塞态。
- 等待使用者輸入的時候,目前線程進入阻塞态。
3.2 分類
-
等待阻塞
運作的線程執行o.wait()方法,JVM會把該線程放入等待隊列(waitting queue)中
-
同步阻塞
運作的線程在擷取對象的同步鎖時,若該同步鎖被别的線程占用,則JVM會把該線程放入鎖池(lock pool)中
-
其他阻塞
運作的線程執行Thread.sleep(long ms)或t.join()方法,或者發出了I/O請求時,JVM會把該線程置為阻塞狀态
當sleep()狀态逾時、join()等待線程終止或者逾時、或者I/O處理完畢時,線程重新轉入可運作(runnable)狀态
線程在阻塞等待monitor lock(螢幕鎖)
一個線程在進入synchronized修飾的臨界區的時候,或者在synchronized臨界區中調用Object.wait然後被喚醒重新進入synchronized臨界區都對應該态。
結合上面RUNNABLE的分析,也就是I/O阻塞不會進入BLOCKED狀态,隻有synchronized會導緻線程進入該狀态
關于BLOCKED狀态,注釋裡隻提到一種情況就是進入synchronized聲明的臨界區時會導緻,這個也很好了解,synchronized是JVM自己控制的,是以這個阻塞事件它自己能夠知道(對比了解上面的作業系統層面)。
interrupt()是無法喚醒的!隻是做個标記而已!
4 等待
線程擁有對象鎖後進入到相應的代碼區後,調用相應的“鎖對象”的
wait()
後産生的一種結果
- 變相的實作
LockSupport.park()
LockSupport parkNanos( )
LockSupport parkUntil( )
Thread join( )
它們也是在等待另一個對象事件的發生,也就是描述了等待的意思。
BLOCKED
狀态也是等待的意思,有什麼關系與差別呢?
BLOCKED
-
是虛拟機認為程式還不能進入某個區域,因為同時進去就會有問題,這是一塊臨界區BLOCKED
-
的先決條件是要進入臨界區,也就是線程已經拿到了“門票”,自己可能進去做了一些事情,但此時通過判定某些業務上的參數(由具體業務決定),發現還有一些其他配合的資源沒有準備充分,那麼自己就等等再做其他的事情wait()
有一個非常典型的案例就是通過
wait()
和
notify()
完成生産者/消費者模型
當生産者生産過快,發現倉庫滿了,即消費者還沒有把東西拿走(空位資源還沒準備好) 時,生産者就等待有空位再做事情,消費者拿走東西時會發出“有空位了”的消息,那麼生産者就又開始工作了
反過來也是一樣,當消費者消費過快發現沒有存貨時,消費者也會等存貨到來,生産者生産出内容後發出“有存貨了”的消息,消費者就又來搶東西了。
在這種狀态下,如果發生了對該線程的
interrupt()
是有用的,處于該狀态的線程内部會抛出一個
InerruptedException
這個異常應當在
run()
裡面捕獲,使得
run()
正常地執行完成。當然在
run()
内部捕獲異常後,還可以讓線程繼續運作,這完全是根據具體的應用場景來決定的。
在這種狀态下,如果某線程對該鎖對象做了
notify()
,那麼将從等待池中喚醒一個線程重新恢複到
RUNNABLE
除
notify()
外,還有一個
notifyAll()
,前者是
喚醒一個處于
WAITING
的線程,而後者是喚醒所有的線程。
Object.wait()是否需要死等呢?
不是,除中斷外,它還有兩個重構方法
- Object.wait(int timeout),傳入的timeout 參數是逾時的毫秒值,超過這個值後會自動喚醒,繼續做下面的操作(不會抛出
,但是并不意味着我們不去捕獲,因為不排除其他線程會對它做InterruptedException
)。interrup()
- Object.wait(int timeout,int nanos) 這是一個更精确的逾時設定,理論上可以精确到納秒,這個納秒值可接受的範圍是0~999999 (因為100000onS 等于1ms)。
同樣的
LockSupport park( )
LockSupport.parkNanos( )
LockSupport.parkUntil( )
Thread.join()
這些方法都會有類似的重構方法來設定逾時,達到類似的目的,不過此時的狀态不再是
WAITING
,而是
TIMED.WAITING
通常寫代碼的人肯定不想讓程式死掉,但是又希望通過這些等待、通知的方式來實作某些平衡,這樣就不得不去嘗試采用“逾時+重試+失敗告知”等方式來達到目的。
TIMED _WAITING
當調用
Thread.sleep()
時,相當于使用某個時間資源作為鎖對象,進而達到等待的目的,當時間達到時觸發線程回到工作狀态。
TERM_INATED
這個線程對象也許是活的,但是,它已經不是一個單獨執行的線程,在一個死去的線程上調用start()方法,會抛
java.lang.IllegalThreadStateException
.
線程run()、main() 方法執行結束,或者因異常退出了run()方法,則該線程結束生命周期。死亡的線程不可再次複生。
run()走完了,線程就處于這種狀态。其實這隻是Java 語言級别的一種狀态,在作業系統内部可能已經登出了相應的線程,或者将它複用給其他需要使用線程的請求,而在Java語言級别隻是通過Java 代碼看到的線程狀态而已。
為什麼 wait( )
notify( )
必須要使用synchronized
wait( )
notify( )
如果不用就會報
ilegalMonitorStateException
常見的寫法如下:
synchronized(Object){
object.wait() ;//object.notify() ;
}
synchronized(this){
this.wait();
}
synchronized fun( ){
this.wait();//this.notify();
}
wait()
和notify()`是基于對象存在的。
-
那為什麼要基于對象存在呢?
既然要等,就要考慮等什麼,這裡等待的就是一個對象發出的信号,是以要基于對象而存在。
不用對象也可以實作,比如suspend()/resume()就不需要,但是它們是反面教材,表面上簡單,但是處處都是問題
了解基于對象的這個道理後,目前認為它調用的方式隻能是
Object.wait()
,這樣才能和對象挂鈎。但這些東西還與問題“wait()/notify() 為什麼必須要使用synchronized" 沒有
半點關系,或者說與對象扯上關系,為什麼非要用鎖呢?
既然是基于對象的,是以它不得不用一個資料結構來存放這些等
待的線程,而且這個資料結構應當是與該對象綁定的(通過檢視C++代碼,發現該資料結構為一個雙向連結清單),此時在這個對象上可能同時有多個線程調用wait()/notify(),在向這個對象所對應的雙向連結清單中寫入、删除資料時,依然存在并發的問題,理論上
也需要一個鎖來控制。在JVM 核心源碼中并沒有發現任何自己用鎖來控制寫入的動作,隻是通過檢查目前線程是否為對象的OWNER 來判定是否要抛出相應的異常。由此可見它希望該動作由Java 程式這個抽象層次來控制,它為什麼不想去自己控制鎖呢?
因為有些時候更低抽象層次的鎖未必是好事,因為這樣的請求對于外部可能是反複循環地去征用,或者這些代碼還可能在其他地方複用,也許将它粗粒度化會更好一些,而且這樣的代在寫在Java 程式中本身也會更加清晰,更加容易看到互相之間的關系。
interrupt()操作隻對處于WAITING 和TIME_WAITING 狀态的線程有用,讓它們]産生實質性的異常抛出。
在通常情況下,如果線程處于運作中狀态,也不會讓它中斷,如果中斷是成立的,可能會導緻正常的業務運作出現問題。另外,如果不想用強制手段,就得為每條代碼的運作設立檢查,但是這個動作很麻煩,JVM 不願意做這件事情,它做interruptl )僅僅是打一個标記,此時程式中通過isInterrupt()方法能夠判定是否被發起過中斷操作,如果被中斷了,那麼如何處理程式就是設計上的事情了。
舉個例子,如果代碼運作是一個死循環,那麼在循環中可以這樣做:
while(true) {
if (Thread.currentThread.isInterrupt()) {
//可以做類似的break、return,抛出InterruptedExcept ion 達到某種目的,這完全由自己決定
//如拋出異常,通常包裝一層try catch 異常處理,進一步做處理,如退出run 方法或什麼也不做
}
}
這太麻煩了,為什麼不可以自動呢?
可以通過一些生活的溝通方式來了解一下: 當你發現門外面有人呼叫你時,你自己是否搭理他是你的事情,這是一種有“愛”的溝通方式,反之是暴力地破門而入,把你強制“抓”出去的方式。
在JDK 1.6 及以後的版本中,可以使用線程的
interrupted( )
判定線程是否已經被調用過中斷方法,表面上的效果與
isInterrupted()
結果一樣,不過這個方法是一個靜态方法
除此之外,更大的差別在于這個方法調用後将會重新将中斷狀态設定為
false
,友善于循環利用線程,而不是中斷後狀态就始終為true,就無法将狀态修改回來了。類似的,判定線程的相關方法還有
isAlive()
isDaemon()
等待隊列
- 調用wait(), notify()前,必須獲得obj鎖,也就是必須寫在synchronized(obj) 代碼段内
- 與等待隊列相關的步驟和圖
- 線程1擷取對象A的鎖,正在使用對象A。
- 線程1調用對象A的wait()方法。
- 線程1釋放對象A的鎖,并馬上進入等待隊列。
- 鎖池裡面的對象争搶對象A的鎖。
- 線程5獲得對象A的鎖,進入synchronized塊,使用對象A。
- 線程5調用對象A的notifyAll()方法,喚醒所有線程,所有線程進入鎖池。|| 線程5調用對象A的notify()方法,喚醒一個線程,不知道會喚醒誰,被喚醒的那個線程進入鎖池。
- notifyAll()方法所在synchronized結束,線程5釋放對象A的鎖。
- 鎖池裡面的線程争搶對象鎖,但線程1什麼時候能搶到就不知道了。|| 原本鎖池+第6步被喚醒的線程一起争搶對象鎖。
鎖池狀态
-
目前線程想調用對象A的同步方法時,發現對象A的鎖被别的線程占有,此時目前線程進入鎖池狀态。
簡言之,鎖池裡面放的都是想
争奪對象鎖的線程
- 當一個線程1被另外一個線程2喚醒時,1線程進入鎖池狀态,去争奪對象鎖。
- 鎖池是在同步的環境下才有的概念,
一個對象對應一個鎖池
幾個方法的比較
-
Thread.sleep(long millis)
一定是目前線程調用此方法,目前線程進入阻塞,不釋放對象鎖,millis後線程自動蘇醒進入可運作态。
作用:給其它線程執行機會的最佳方式。
-
Thread.yield()
一定是目前線程調用此方法,目前線程放棄擷取的cpu時間片,由運作狀态變會可運作狀态,讓OS再次選擇線程。
作用:讓相同優先級的線程輪流執行,但并不保證一定會輪流執行。實際中無法保證yield()達到讓步目的,因為讓步的線程還有可能被線程排程程式再次選中。Thread.yield()不會導緻阻塞。
- t.join()/t.join(long millis),目前線程裡調用其它線程1的join方法,目前線程阻塞,但不釋放對象鎖,直到線程1執行完畢或者millis時間到,目前線程進入可運作狀态。
- obj.wait(),目前線程調用對象的wait()方法,目前線程釋放對象鎖,進入等待隊列。依靠notify()/notifyAll()喚醒或者wait(long timeout)timeout時間到自動喚醒。
- obj.notify()喚醒在此對象螢幕上等待的單個線程,選擇是任意性的。notifyAll()喚醒在此對象螢幕上等待的所有線程。
疑問
- 當對象鎖被某一線程釋放的一瞬間,鎖池裡面的哪個線程能獲得這個鎖?随機?隊列FIFO?or sth else?
- 等待隊列裡許許多多的線程都wait()在一個對象上,此時某一線程調用了對象的notify()方法,那喚醒的到底是哪個線程?随機?隊列FIFO?or sth else?java文檔就簡單的寫了句:選擇是任意性的。