0 線程狀態(tài)概述
分類
6個狀態(tài)定義: java.lang.Thread.State
- New: 尚未啟動的線程的線程狀態(tài)措伐。
- Runnable: 可運行線程的線程狀態(tài)杭措,等待CPU調(diào)度跟继。
- Blocked: 線程阻塞等待監(jiān)視器鎖定的線程狀態(tài)拾枣。
處于synchronized同步代碼塊或方法中被阻塞亏娜。 - Waiting: 等待線程的線程狀態(tài)职辅。下 列不帶超時的方式:
Object.wait、Thread.join唬滑、 LockSupport.park - Timed Waiting:具有指定等待時間的等待線程的線程狀態(tài)告唆。下 列帶超時的方式:
Thread.sleep、0bject.wait晶密、 Thread.join擒悬、 LockSupport.parkNanos、 LockSupport.parkUntil - 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()
除此之外斑匪,更大的區(qū)別在于這個方法調(diào)用后將會重新將中斷狀態(tài)設(shè)置為
false
,方便于循環(huán)利用線程,而不是中斷后狀態(tài)就始終為true,就無法將狀態(tài)修改回來了锋勺。類似的蚀瘸,判定線程的相關(guān)方法還有isAlive()
isDaemon()
等待隊列
- 調(diào)用wait(), notify()前,必須獲得obj鎖庶橱,也就是必須寫在synchronized(obj) 代碼段內(nèi)
- 與等待隊列相關(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)
- 當前線程想調(diào)用對象A的同步方法時趾唱,發(fā)現(xiàn)對象A的鎖被別的線程占有涌乳,此時當前線程進入鎖池狀態(tài)。
簡言之甜癞,鎖池里面放的都是想爭奪對象鎖的線程
- 當一個線程1被另外一個線程2喚醒時夕晓,1線程進入鎖池狀態(tài),去爭奪對象鎖悠咱。
- 鎖池是在同步的環(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)致阻塞肋坚。
- t.join()/t.join(long millis),當前線程里調(diào)用其它線程1的join方法肃廓,當前線程阻塞智厌,但不釋放對象鎖,直到線程1執(zhí)行完畢或者millis時間到盲赊,當前線程進入可運行狀態(tài)铣鹏。
- obj.wait(),當前線程調(diào)用對象的wait()方法哀蘑,當前線程釋放對象鎖诚卸,進入等待隊列。依靠notify()/notifyAll()喚醒或者wait(long timeout)timeout時間到自動喚醒绘迁。
- obj.notify()喚醒在此對象監(jiān)視器上等待的單個線程合溺,選擇是任意性的。notifyAll()喚醒在此對象監(jiān)視器上等待的所有線程缀台。
疑問
- 當對象鎖被某一線程釋放的一瞬間棠赛,鎖池里面的哪個線程能獲得這個鎖?隨機?隊列FIFO恭朗?or sth else屏镊?
- 等待隊列里許許多多的線程都wait()在一個對象上,此時某一線程調(diào)用了對象的notify()方法痰腮,那喚醒的到底是哪個線程而芥?隨機?隊列FIFO膀值?or sth else棍丐?java文檔就簡單的寫了句:選擇是任意性的。