天天看點

深入了解Java線程狀态0 線程狀态概述1 NEW2 RUNNABLE3 BLOCKED4 等待TIMED _WAITINGTERM_INATED等待隊列鎖池狀态幾個方法的比較疑問

贊助平台

首頁 / 文章管理 / 文章編輯

Java線程狀态

友情提示:文章每30秒自動儲存一次,編輯器支援圖檔拖動上傳或者複制粘貼上傳~

0 線程狀态概述

分類

6個狀态定義: java.lang.Thread.State

  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: 終止線程的線程狀态。線程正常完成執行或者出現異常。

流程圖

深入了解Java線程狀态0 線程狀态概述1 NEW2 RUNNABLE3 BLOCKED4 等待TIMED _WAITINGTERM_INATED等待隊列鎖池狀态幾個方法的比較疑問

1 NEW

深入了解Java線程狀态0 線程狀态概述1 NEW2 RUNNABLE3 BLOCKED4 等待TIMED _WAITINGTERM_INATED等待隊列鎖池狀态幾個方法的比較疑問

實作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

    是虛拟機認為程式還不能進入某個區域,因為同時進去就會有問題,這是一塊臨界區
  • 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文檔就簡單的寫了句:選擇是任意性的。