Java線程狀態

0 線程狀態概述

分類

6個狀態定義: java.lang.Thread.Statejava

  1. New: 還沒有啓動的線程的線程狀態。
  2. Runnable: 可運行線程的線程狀態,等待CPU調度。
  3. Blocked: 線程阻塞等待監視器鎖定的線程狀態。
    處於synchronized同步代碼塊或方法中被阻塞。
  4. Waiting: 等待線程的線程狀態。下 列不帶超時的方式:
    Object.wait、Thread.join、 LockSupport.park
  5. Timed Waiting:具備指定等待時間的等待線程的線程狀態。下 列帶超時的方式:
    Thread.sleep、0bject.wait、 Thread.join、 LockSupport.parkNanos、 LockSupport.parkUntil
  6. Terminated: 終止線程的線程狀態。線程正常完成執行或者出現異常。

流程圖

1 NEW

實現Runnable接口和繼承Thread能夠獲得一個線程類,new一個實例出來,線程就進入了初始狀態數據結構

線程仍是沒有開始執行多線程

有狀態了,那確定是已經建立好線程對象了(若是對象都沒有,何來狀態這說),問題的焦點就在於尚未開始執行,當調用線程的start()方法時,線程不必定會立刻執行,由於Java線程是映射到操做系統的線程執行,此時可能還須要等操做系統調度,但此時該線程的狀態已經爲RUNNABLE併發

2 RUNNABLE

只是說你有資格運行,調度程序沒有挑選到你,你就永遠是可運行狀態。學習

2.1條件

  • 調用start(),進入可運行態
  • 當前線程sleep()結束,其餘線程join()結束,等待用戶輸入完畢,某個線程拿到對象鎖,這些線程也將進入可運行狀態
  • 當前線程時間片用完,調用當前線程的yield()方法,當前線程進入可運行狀態
  • 鎖池裏的線程拿到對象鎖後,進入可運行狀態
  • 正在執行線程必屬於此態

這個狀態是最有爭議的,註釋中說了,它表示線程在JVM層面是執行的,但在操做系統層面不必定,它舉例是CPU,毫無疑問CPU是一個操做系統資源,但這也就意味着在等操做系統其餘資源的時候,線程也會是這個狀態this

這裏就有一個關鍵點IO阻塞算是等操做系統的資源?spa

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 是虛擬機認爲程序還不能進入某個區域,由於同時進去就會有問題,這是一塊臨界區
  • 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

若是不用就會報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()線程的狀態圖

等待隊列

  1. 調用wait(), notify()前,必須得到obj鎖,也就是必須寫在synchronized(obj) 代碼段內
  2. 與等待隊列相關的步驟和圖
  • 線程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步被喚醒的線程一塊兒爭搶對象鎖。多線程等待隊列

鎖池狀態

  1. 當前線程想調用對象A的同步方法時,發現對象A的鎖被別的線程佔有,此時當前線程進入鎖池狀態。
    簡言之,鎖池裏面放的都是想爭奪對象鎖的線程
  2. 當一個線程1被另一個線程2喚醒時,1線程進入鎖池狀態,去爭奪對象鎖。
  3. 鎖池是在同步的環境下才有的概念,一個對象對應一個鎖池

幾個方法的比較

  • Thread.sleep(long millis)
    必定是當前線程調用此方法,當前線程進入阻塞,不釋放對象鎖,millis後線程自動甦醒進入可運行態。
    做用:給其它線程執行機會的最佳方式。
  • Thread.yield()
    必定是當前線程調用此方法,當前線程放棄獲取的cpu時間片,由運行狀態變會可運行狀態,讓OS再次選擇線程。
    做用:讓相同優先級的線程輪流執行,但並不保證必定會輪流執行。實際中沒法保證yield()達到讓步目的,由於讓步的線程還有可能被線程調度程序再次選中。Thread.yield()不會致使阻塞。
  1. t.join()/t.join(long millis),當前線程裏調用其它線程1的join方法,當前線程阻塞,但不釋放對象鎖,直到線程1執行完畢或者millis時間到,當前線程進入可運行狀態。
  2. obj.wait(),當前線程調用對象的wait()方法,當前線程釋放對象鎖,進入等待隊列。依靠notify()/notifyAll()喚醒或者wait(long timeout)timeout時間到自動喚醒。
  3. obj.notify()喚醒在此對象監視器上等待的單個線程,選擇是任意性的。notifyAll()喚醒在此對象監視器上等待的全部線程。

疑問

  1. 當對象鎖被某一線程釋放的一瞬間,鎖池裏面的哪一個線程能得到這個鎖?隨機?隊列FIFO?or sth else?
  2. 等待隊列裏許許多多的線程都wait()在一個對象上,此時某一線程調用了對象的notify()方法,那喚醒的究竟是哪一個線程?隨機?隊列FIFO?or sth else?java文檔就簡單的寫了句:選擇是任意性的。

X 交流學習

相關文章
相關標籤/搜索