Java線程狀態(tài)

0 線程狀態(tài)概述

分類

6個狀態(tài)定義: java.lang.Thread.State

  1. New: 尚未啟動的線程的線程狀態(tài)措伐。
  2. Runnable: 可運行線程的線程狀態(tài)杭措,等待CPU調(diào)度跟继。
  3. Blocked: 線程阻塞等待監(jiān)視器鎖定的線程狀態(tài)拾枣。
    處于synchronized同步代碼塊或方法中被阻塞亏娜。
  4. Waiting: 等待線程的線程狀態(tài)职辅。下 列不帶超時的方式:
    Object.wait、Thread.join唬滑、 LockSupport.park
  5. Timed Waiting:具有指定等待時間的等待線程的線程狀態(tài)告唆。下 列帶超時的方式:
    Thread.sleep、0bject.wait晶密、 Thread.join擒悬、 LockSupport.parkNanos、 LockSupport.parkUntil
  6. Terminated: 終止線程的線程狀態(tài)稻艰。線程正常完成執(zhí)行或者出現(xiàn)異常懂牧。

流程圖

1 NEW


實現(xiàn)Runnable接口和繼承Thread可以得到一個線程類,new一個實例出來尊勿,線程就進入了初始狀態(tài)

線程還是沒有開始執(zhí)行

有狀態(tài)了僧凤,那肯定是已經(jīng)創(chuàng)建好線程對象了(如果對象都沒有,何來狀態(tài)這說)元扔,
問題的焦點就在于還沒有開始執(zhí)行躯保,當調(diào)用線程的start()方法時,線程不一定會馬上執(zhí)行澎语,因為Java線程是映射到操作系統(tǒng)的線程執(zhí)行途事,此時可能還需要等操作系統(tǒng)調(diào)度验懊,但此時該線程的狀態(tài)已經(jīng)為RUNNABLE

2 RUNNABLE

只是說你有資格運行,調(diào)度程序沒有挑選到你尸变,你就永遠是可運行狀態(tài)义图。

2.1條件

  • 調(diào)用start(),進入可運行態(tài)
  • 當前線程sleep()結(jié)束召烂,其他線程join()結(jié)束碱工,等待用戶輸入完畢,某個線程拿到對象鎖骑晶,這些線程也將進入可運行狀態(tài)
  • 當前線程時間片用完痛垛,調(diào)用當前線程的yield()方法,當前線程進入可運行狀態(tài)
  • 鎖池里的線程拿到對象鎖后桶蛔,進入可運行狀態(tài)
  • 正在執(zhí)行線程必屬于此態(tài)

這個狀態(tài)是最有爭議的匙头,注釋中說了,它表示線程在JVM層面是執(zhí)行的仔雷,但在操作系統(tǒng)層面不一定蹂析,它舉例是CPU,毫無疑問CPU是一個操作系統(tǒng)資源碟婆,但這也就意味著在等操作系統(tǒng)其他資源的時候电抚,線程也會是這個狀態(tài)

這里就有一個關(guān)鍵點IO阻塞算是等操作系統(tǒng)的資源?

3 BLOCKED


被掛起,線程因為某種原因放棄了cpu timeslice竖共,暫時停止運行蝙叛。

3.1條件

  • 當前線程調(diào)用Thread.sleep(),進入阻塞態(tài)
  • 運行在當前線程里的其它線程調(diào)用join()公给,當前線程進入阻塞態(tài)借帘。
  • 等待用戶輸入的時候,當前線程進入阻塞態(tài)淌铐。

3.2 分類

  • 等待阻塞
    運行的線程執(zhí)行o.wait()方法肺然,JVM會把該線程放入等待隊列(waitting queue)中

  • 同步阻塞
    運行的線程在獲取對象的同步鎖時,若該同步鎖被別的線程占用腿准,則JVM會把該線程放入鎖池(lock pool)中

  • 其他阻塞
    運行的線程執(zhí)行Thread.sleep(long ms)或t.join()方法际起,或者發(fā)出了I/O請求時,JVM會把該線程置為阻塞狀態(tài)
    當sleep()狀態(tài)超時吐葱、join()等待線程終止或者超時街望、或者I/O處理完畢時,線程重新轉(zhuǎn)入可運行(runnable)狀態(tài)

線程在阻塞等待monitor lock(監(jiān)視器鎖)
一個線程在進入synchronized修飾的臨界區(qū)的時候,或者在synchronized臨界區(qū)中調(diào)用Object.wait然后被喚醒重新進入synchronized臨界區(qū)都對應(yīng)該態(tài)弟跑。

結(jié)合上面RUNNABLE的分析,也就是I/O阻塞不會進入BLOCKED狀態(tài),只有synchronized會導(dǎo)致線程進入該狀態(tài)

關(guān)于BLOCKED狀態(tài)它匕,注釋里只提到一種情況就是進入synchronized聲明的臨界區(qū)時會導(dǎo)致,這個也很好理解窖认,synchronized是JVM自己控制的豫柬,所以這個阻塞事件它自己能夠知道(對比理解上面的操作系統(tǒng)層面)。

interrupt()是無法喚醒的!只是做個標記而已!

4 等待


線程擁有對象鎖后進入到相應(yīng)的代碼區(qū)后扑浸,調(diào)用相應(yīng)的“鎖對象”的wait()后產(chǎn)生的一種結(jié)果

  • 變相的實現(xiàn)
    LockSupport.park()
    LockSupport parkNanos( )
    LockSupport parkUntil( )
    Thread join( )

它們也是在等待另一個對象事件的發(fā)生烧给,也就是描述了等待的意思。

BLOCKED狀態(tài)也是等待的意思喝噪,有什么關(guān)系與區(qū)別呢?

  • BLOCKED是虛擬機認為程序還不能進入某個區(qū)域础嫡,因為同時進去就會有問題,這是一塊臨界區(qū)
  • wait()的先決條件是要進入臨界區(qū)酝惧,也就是線程已經(jīng)拿到了“門票”榴鼎,自己可能進去做了一些事情,但此時通過判定某些業(yè)務(wù)上的參數(shù)(由具體業(yè)務(wù)決定),發(fā)現(xiàn)還有一些其他配合的資源沒有準備充分晚唇,那么自己就等等再做其他的事情

有一個非常典型的案例就是通過wait()notify()完成生產(chǎn)者/消費者模型
當生產(chǎn)者生產(chǎn)過快巫财,發(fā)現(xiàn)倉庫滿了,即消費者還沒有把東西拿走(空位資源還沒準備好) 時哩陕,生產(chǎn)者就等待有空位再做事情平项,消費者拿走東西時會發(fā)出“有空位了”的消息,那么生產(chǎn)者就又開始工作了
反過來也是一樣悍及,當消費者消費過快發(fā)現(xiàn)沒有存貨時闽瓢,消費者也會等存貨到來,生產(chǎn)者生產(chǎn)出內(nèi)容后發(fā)出“有存貨了”的消息心赶,消費者就又來搶東西了扣讼。

在這種狀態(tài)下,如果發(fā)生了對該線程的interrupt()是有用的缨叫,處于該狀態(tài)的線程內(nèi)部會拋出一個InerruptedException
這個異常應(yīng)當在run()里面捕獲椭符,使得run()正常地執(zhí)行完成。當然在run()內(nèi)部捕獲異常后弯汰,還可以讓線程繼續(xù)運行艰山,這完全是根據(jù)具體的應(yīng)用場景來決定的。

在這種狀態(tài)下咏闪,如果某線程對該鎖對象做了notify()曙搬,那么將從等待池中喚醒一個線程重新恢復(fù)到RUNNABLE
notify()外,還有一個notifyAll() 鸽嫂,前者是
喚醒一個處于WAITING的線程纵装,而后者是喚醒所有的線程。

Object.wait()是否需要死等呢?



不是据某,除中斷外橡娄,它還有兩個重構(gòu)方法

  • Object.wait(int timeout),傳入的timeout 參數(shù)是超時的毫秒值,超過這個值后會自動喚醒癣籽,繼續(xù)做下面的操作(不會拋出InterruptedException 挽唉,但是并不意味著我們不去捕獲滤祖,因為不排除其他線程會對它做interrup())。
  • Object.wait(int timeout,int nanos) 這是一個更精確的超時設(shè)置瓶籽,理論上可以精確到納秒匠童,這個納秒值可接受的范圍是0~999999 (因為100000onS 等于1ms)。

同樣的
LockSupport park( )
LockSupport.parkNanos( )
LockSupport.parkUntil( )
Thread.join()
這些方法都會有類似的重構(gòu)方法來設(shè)置超時塑顺,達到類似的目的汤求,不過此時的狀態(tài)不再是WAITING,而是TIMED.WAITING

通常寫代碼的人肯定不想讓程序死掉,但是又希望通過這些等待严拒、通知的方式來實現(xiàn)某些平衡扬绪,這樣就不得不去嘗試采用“超時+重試+失敗告知”等方式來達到目的。

TIMED _WAITING


當調(diào)用Thread.sleep()時,相當于使用某個時間資源作為鎖對象裤唠,進而達到等待的目的挤牛,當時間達到時觸發(fā)線程回到工作狀態(tài)。

TERM_INATED


這個線程對象也許是活的巧骚,但是赊颠,它已經(jīng)不是一個單獨執(zhí)行的線程,在一個死去的線程上調(diào)用start()方法,會拋java.lang.IllegalThreadStateException.
線程run()劈彪、main() 方法執(zhí)行結(jié)束竣蹦,或者因異常退出了run()方法,則該線程結(jié)束生命周期沧奴。死亡的線程不可再次復(fù)生痘括。
run()走完了,線程就處于這種狀態(tài)滔吠。其實這只是Java 語言級別的一種狀態(tài)纲菌,在操作系統(tǒng)內(nèi)部可能已經(jīng)注銷了相應(yīng)的線程,或者將它復(fù)用給其他需要使用線程的請求疮绷,而在Java語言級別只是通過Java 代碼看到的線程狀態(tài)而已翰舌。

為什么wait( )notify( )必須要使用synchronized

如果不用就會報ilegalMonitorStateException
常見的寫法如下:

synchronized(Object){
    object.wait() ;//object.notify() ;
}

synchronized(this){
    this.wait();
}
synchronized fun( ){
    this.wait();//this.notify();
}

wait()和notify()`是基于對象存在的。

  • 那為什么要基于對象存在呢?
    既然要等冬骚,就要考慮等什么椅贱,這里等待的就是一個對象發(fā)出的信號,所以要基于對象而存在只冻。
    不用對象也可以實現(xiàn)庇麦,比如suspend()/resume()就不需要,但是它們是反面教材喜德,表面上簡單山橄,但是處處都是問題

理解基于對象的這個道理后,目前認為它調(diào)用的方式只能是Object.wait()舍悯,這樣才能和對象掛鉤航棱。但這些東西還與問題“wait()/notify() 為什么必須要使用synchronized" 沒有
半點關(guān)系睡雇,或者說與對象扯上關(guān)系,為什么非要用鎖呢?

既然是基于對象的饮醇,因此它不得不用一個數(shù)據(jù)結(jié)構(gòu)來存放這些等
待的線程入桂,而且這個數(shù)據(jù)結(jié)構(gòu)應(yīng)當是與該對象綁定的(通過查看C++代碼,發(fā)現(xiàn)該數(shù)據(jù)結(jié)構(gòu)為一個雙向鏈表)驳阎,此時在這個對象上可能同時有多個線程調(diào)用wait()/notify(),在向這個對象所對應(yīng)的雙向鏈表中寫入、刪除數(shù)據(jù)時馁蒂,依然存在并發(fā)的問題呵晚,理論上
也需要一個鎖來控制。在JVM 內(nèi)核源碼中并沒有發(fā)現(xiàn)任何自己用鎖來控制寫入的動作沫屡,只是通過檢查當前線程是否為對象的OWNER 來判定是否要拋出相應(yīng)的異常饵隙。由此可見它希望該動作由Java 程序這個抽象層次來控制,它為什么不想去自己控制鎖呢?
因為有些時候更低抽象層次的鎖未必是好事沮脖,因為這樣的請求對于外部可能是反復(fù)循環(huán)地去征用金矛,或者這些代碼還可能在其他地方復(fù)用,也許將它粗粒度化會更好一些勺届,而且這樣的代在寫在Java 程序中本身也會更加清晰驶俊,更加容易看到相互之間的關(guān)系。

interrupt()操作只對處于WAITING 和TIME_WAITING 狀態(tài)的線程有用免姿,讓它們]產(chǎn)生實質(zhì)性的異常拋出饼酿。
在通常情況下,如果線程處于運行中狀態(tài)胚膊,也不會讓它中斷故俐,如果中斷是成立的,可能會導(dǎo)致正常的業(yè)務(wù)運行出現(xiàn)問題紊婉。另外药版,如果不想用強制手段,就得為每條代碼的運行設(shè)立檢查喻犁,但是這個動作很麻煩槽片,JVM 不愿意做這件事情,它做interruptl )僅僅是打一個標記株汉,此時程序中通過isInterrupt()方法能夠判定是否被發(fā)起過中斷操作筐乳,如果被中斷了,那么如何處理程序就是設(shè)計上的事情了乔妈。

舉個例子蝙云,如果代碼運行是一個死循環(huán),那么在循環(huán)中可以這樣做:

while(true) {
    if (Thread.currentThread.isInterrupt()) {
    //可以做類似的break路召、return,拋出InterruptedExcept ion 達到某種目的勃刨,這完全由自己決定
    //如拋出異常波材,通常包裝一層try catch 異常處理,進一步做處理身隐,如退出run 方法或什么也不做
    }
}

這太麻煩了廷区,為什么不可以自動呢?
可以通過一些生活的溝通方式來理解一下: 當你發(fā)現(xiàn)門外面有人呼叫你時,你自己是否搭理他是你的事情贾铝,這是一種有“愛”的溝通方式隙轻,反之是暴力地破門而入,把你強制“抓”出去的方式垢揩。

在JDK 1.6 及以后的版本中玖绿,可以使用線程的interrupted( )

判定線程是否已經(jīng)被調(diào)用過中斷方法,表面上的效果與isInterrupted()

結(jié)果一樣叁巨,不過這個方法是一個靜態(tài)方法
除此之外斑匪,更大的區(qū)別在于這個方法調(diào)用后將會重新將中斷狀態(tài)設(shè)置為false,方便于循環(huán)利用線程,而不是中斷后狀態(tài)就始終為true,就無法將狀態(tài)修改回來了锋勺。類似的蚀瘸,判定線程的相關(guān)方法還有isAlive()

isDaemon()

線程的狀態(tài)圖

等待隊列

  1. 調(diào)用wait(), notify()前,必須獲得obj鎖庶橱,也就是必須寫在synchronized(obj) 代碼段內(nèi)
  2. 與等待隊列相關(guān)的步驟和圖
  • 線程1獲取對象A的鎖贮勃,正在使用對象A。
  • 線程1調(diào)用對象A的wait()方法悬包。
  • 線程1釋放對象A的鎖衙猪,并馬上進入等待隊列。
  • 鎖池里面的對象爭搶對象A的鎖布近。
  • 線程5獲得對象A的鎖垫释,進入synchronized塊,使用對象A撑瞧。
  • 線程5調(diào)用對象A的notifyAll()方法棵譬,喚醒所有線程,所有線程進入鎖池预伺。|| 線程5調(diào)用對象A的notify()方法订咸,喚醒一個線程,不知道會喚醒誰酬诀,被喚醒的那個線程進入鎖池脏嚷。
  • notifyAll()方法所在synchronized結(jié)束,線程5釋放對象A的鎖瞒御。
  • 鎖池里面的線程爭搶對象鎖父叙,但線程1什么時候能搶到就不知道了。|| 原本鎖池+第6步被喚醒的線程一起爭搶對象鎖。
    多線程等待隊列

鎖池狀態(tài)

  1. 當前線程想調(diào)用對象A的同步方法時趾唱,發(fā)現(xiàn)對象A的鎖被別的線程占有涌乳,此時當前線程進入鎖池狀態(tài)。
    簡言之甜癞,鎖池里面放的都是想爭奪對象鎖的線程
  2. 當一個線程1被另外一個線程2喚醒時夕晓,1線程進入鎖池狀態(tài),去爭奪對象鎖悠咱。
  3. 鎖池是在同步的環(huán)境下才有的概念蒸辆,一個對象對應(yīng)一個鎖池

幾個方法的比較

  • Thread.sleep(long millis)
    一定是當前線程調(diào)用此方法,當前線程進入阻塞析既,不釋放對象鎖吁朦,millis后線程自動蘇醒進入可運行態(tài)。
    作用:給其它線程執(zhí)行機會的最佳方式渡贾。
  • Thread.yield()
    一定是當前線程調(diào)用此方法,當前線程放棄獲取的cpu時間片雄右,由運行狀態(tài)變會可運行狀態(tài)空骚,讓OS再次選擇線程。
    作用:讓相同優(yōu)先級的線程輪流執(zhí)行擂仍,但并不保證一定會輪流執(zhí)行囤屹。實際中無法保證yield()達到讓步目的,因為讓步的線程還有可能被線程調(diào)度程序再次選中逢渔。Thread.yield()不會導(dǎo)致阻塞肋坚。
  1. t.join()/t.join(long millis),當前線程里調(diào)用其它線程1的join方法肃廓,當前線程阻塞智厌,但不釋放對象鎖,直到線程1執(zhí)行完畢或者millis時間到盲赊,當前線程進入可運行狀態(tài)铣鹏。
  2. obj.wait(),當前線程調(diào)用對象的wait()方法哀蘑,當前線程釋放對象鎖诚卸,進入等待隊列。依靠notify()/notifyAll()喚醒或者wait(long timeout)timeout時間到自動喚醒绘迁。
  3. obj.notify()喚醒在此對象監(jiān)視器上等待的單個線程合溺,選擇是任意性的。notifyAll()喚醒在此對象監(jiān)視器上等待的所有線程缀台。

疑問

  1. 當對象鎖被某一線程釋放的一瞬間棠赛,鎖池里面的哪個線程能獲得這個鎖?隨機?隊列FIFO恭朗?or sth else屏镊?
  2. 等待隊列里許許多多的線程都wait()在一個對象上,此時某一線程調(diào)用了對象的notify()方法痰腮,那喚醒的到底是哪個線程而芥?隨機?隊列FIFO膀值?or sth else棍丐?java文檔就簡單的寫了句:選擇是任意性的。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末沧踏,一起剝皮案震驚了整個濱河市歌逢,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌翘狱,老刑警劉巖秘案,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異潦匈,居然都是意外死亡阱高,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進店門茬缩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來赤惊,“玉大人,你說我怎么就攤上這事凰锡∥粗郏” “怎么了?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵掂为,是天一觀的道長裕膀。 經(jīng)常有香客問我,道長勇哗,這世上最難降的妖魔是什么魂角? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮智绸,結(jié)果婚禮上野揪,老公的妹妹穿的比我還像新娘。我一直安慰自己瞧栗,他們只是感情好斯稳,可當我...
    茶點故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著迹恐,像睡著了一般挣惰。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天憎茂,我揣著相機與錄音珍语,去河邊找鬼。 笑死竖幔,一個胖子當著我的面吹牛板乙,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播拳氢,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼募逞,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了馋评?” 一聲冷哼從身側(cè)響起放接,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎留特,沒想到半個月后纠脾,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡蜕青,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年乳乌,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片市咆。...
    茶點故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖再来,靈堂內(nèi)的尸體忽然破棺而出蒙兰,到底是詐尸還是另有隱情,我是刑警寧澤芒篷,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布搜变,位于F島的核電站,受9級特大地震影響针炉,放射性物質(zhì)發(fā)生泄漏挠他。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一篡帕、第九天 我趴在偏房一處隱蔽的房頂上張望殖侵。 院中可真熱鬧,春花似錦镰烧、人聲如沸拢军。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽茉唉。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間度陆,已是汗流浹背艾凯。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留懂傀,地道東北人趾诗。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像鸿竖,于是被迫代替她去往敵國和親沧竟。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,792評論 2 345

推薦閱讀更多精彩內(nèi)容