線程在一定條件下,狀態(tài)會發(fā)生變化。線程一共有以下幾種狀態(tài):
1、新建狀態(tài)(New):新創(chuàng)建了一個線程對象。
2、就緒狀態(tài)(Runnable):線程對象創(chuàng)建后,其他線程調(diào)用了該對象的start()方法。該狀態(tài)的線程位于“可運行線程池”中,變得可運行,只等待獲取CPU的使用權(quán)。即在就緒狀態(tài)的進程除CPU之外,其它的運行所需資源都已全部獲得。
3、運行狀態(tài)(Running):就緒狀態(tài)的線程獲取了CPU,執(zhí)行程序代碼。
4、阻塞狀態(tài)(Blocked):阻塞狀態(tài)是線程因為某種原因放棄CPU使用權(quán),暫時停止運行。直到線程進入就緒狀態(tài),才有機會轉(zhuǎn)到運行狀態(tài)。
阻塞的情況分三種:
- (1)、等待阻塞:運行的線程執(zhí)行wait()方法,該線程會釋放占用的所有資源,JVM會把該線程放入“等待池”中。進入這個狀態(tài)后,是不能自動喚醒的,必須依靠其他線程調(diào)用notify()或notifyAll()方法才能被喚醒,
- (2)、同步阻塞:運行的線程在獲取對象的同步鎖時,若該同步鎖被別的線程占用,則JVM會把該線程放入“鎖池”中。
- (3)、其他阻塞:運行的線程執(zhí)行sleep()或join()方法,或者發(fā)出了I/O請求時,JVM會把該線程置為阻塞狀態(tài)。當(dāng)sleep()狀態(tài)超時、join()等待線程終止或者超時、或者I/O處理完畢時,線程重新轉(zhuǎn)入就緒狀態(tài)。
5、死亡狀態(tài)(Dead):線程執(zhí)行完了或者因異常退出了run()方法,該線程結(jié)束生命周期。
線程變化的狀態(tài)轉(zhuǎn)換圖如下:
注:拿到對象的鎖標(biāo)記,即為獲得了對該對象(臨界區(qū))的使用權(quán)限。即該線程獲得了運行所需的資源,進入“就緒狀態(tài)”,只需獲得CPU,就可以運行。因為當(dāng)調(diào)用wait()后,線程會釋放掉它所占有的“鎖標(biāo)志”,所以線程只有在此獲取資源才能進入就緒狀態(tài)。
下面小小的作下解釋:
- 1、線程的實現(xiàn)有兩種方式,一是繼承Thread類,二是實現(xiàn)Runnable接口,但不管怎樣, 當(dāng)我們new了這個對象后,線程就進入了初始狀態(tài);
- 2、當(dāng)該對象調(diào)用了start()方法,就進入就緒狀態(tài);
- 3、進入就緒后,當(dāng)該對象被操作系統(tǒng)選中,獲得CPU時間片就會進入運行狀態(tài);
- 4、進入運行狀態(tài)后情況就比較復(fù)雜了
- 4.1、run()方法或main()方法結(jié)束后,線程就進入終止?fàn)顟B(tài);
- 4.2、當(dāng)線程調(diào)用了自身的sleep()方法或其他線程的join()方法,進程讓出CPU,然后就會進入阻塞狀態(tài)(該狀態(tài)既停止當(dāng)前線程,但并不釋放所占有的資源****即調(diào)用sleep ()函數(shù)后,線程不會釋放它的“鎖標(biāo)志”。)。當(dāng)sleep()結(jié)束或join()結(jié)束后,該線程進入可運行狀態(tài),繼續(xù)等待OS分配CPU時間片。典型地,****sleep()被用在等待某個資源就緒的情形:測試發(fā)現(xiàn)條件不滿足后,讓線程阻塞一段時間后重新測試,直到條件滿足為止。
- 4.3、線程調(diào)用了yield()方法,意思是放棄當(dāng)前獲得的CPU時間片,回到就緒狀態(tài),這時與其他進程處于同等競爭狀態(tài),OS有可能會接著又讓這個進程進入運行狀態(tài); 調(diào)用 yield() 的效果等價于調(diào)度程序認(rèn)為該線程已執(zhí)行了足夠的時間片從而需要轉(zhuǎn)到另一個線程。yield()只是使當(dāng)前線程重新回到可執(zhí)行狀態(tài),所以執(zhí)行yield()的線程有可能在進入到可執(zhí)行狀態(tài)后馬上又被執(zhí)行。
- 4.4、當(dāng)線程剛進入可運行狀態(tài)(注意,還沒運行),發(fā)現(xiàn)將要調(diào)用的資源被synchroniza(同步),獲取不到鎖標(biāo)記,將會立即進入鎖池狀態(tài),等待獲取鎖標(biāo)記(這時的鎖池里也許已經(jīng)有了其他線程在等待獲取鎖標(biāo)記,這時它們處于隊列狀態(tài),既先到先得),一旦線程獲得鎖標(biāo)記后,就轉(zhuǎn)入就緒狀態(tài),等待OS分配CPU時間片;
- 4.5、suspend() 和 resume()方法:兩個方法配套使用,suspend()使得線程進入阻塞狀態(tài),并且不會自動恢復(fù),必須其對應(yīng)的resume()被調(diào)用,才能使得線程重新進入可執(zhí)行狀態(tài)。典型地,****suspend()和 resume() 被用在等待另一個線程產(chǎn)生的結(jié)果的情形:**測試發(fā)現(xiàn)結(jié)果還沒有產(chǎn)生后,讓線程阻塞,另一個線程產(chǎn)生了結(jié)果后,調(diào)用 resume()使其恢復(fù)。
- ** 4.6****、wait()和 notify() 方法:當(dāng)線程調(diào)用wait()方法后會進入等待隊列(進入這個狀態(tài)會釋放所占有的所有資源,與阻塞狀態(tài)不同**),進入這個狀態(tài)后,是不能自動喚醒的,必須依靠其他線程調(diào)用notify()或notifyAll()方法才能被喚醒(由于notify()只是喚醒一個線程,但我們由不能確定具體喚醒的是哪一個線程,也許我們需要喚醒的線程不能夠被喚醒,因此在實際使用時,一般都用notifyAll()方法,喚醒有所線程),線程被喚醒后會進入鎖池,等待獲取鎖標(biāo)記。
wait() 使得線程進入阻塞狀態(tài),它有兩種形式:
- 一種允許指定以毫秒為單位的一段時間作為參數(shù);
- 另一種沒有參數(shù)。
前者當(dāng)對應(yīng)的 notify()被調(diào)用或者超出指定時間時線程重新進入可執(zhí)行狀態(tài)即就緒狀態(tài),后者則必須對應(yīng)的 notify()被調(diào)用。當(dāng)調(diào)用wait()后,線程會釋放掉它所占有的“鎖標(biāo)志”,從而使線程所在對象中的其它synchronized數(shù)據(jù)可被別的線程使用。waite()和notify()因為會對對象的“鎖標(biāo)志”進行操作,所以它們必須在synchronized函數(shù)或synchronizedblock中進行調(diào)用。如果在non-synchronized函數(shù)或non-synchronizedblock中進行調(diào)用,雖然能編譯通過,但在運行時會發(fā)生IllegalMonitorStateException的異常。
注意區(qū)別:初看起來wait() 和 notify() 方法與suspend()和 resume() 方法對沒有什么分別,但是事實上它們是截然不同的。區(qū)別的核心在于,前面敘述的suspend()及其它所有方法在線程阻塞時都不會釋放占用的鎖(如果占用了的話),而wait() 和 notify() 這一對方法則相反。
上述的核心區(qū)別導(dǎo)致了一系列的細節(jié)上的區(qū)別
- 首先,前面敘述的所有方法都隸屬于 Thread類,但是wait() 和 notify() 方法這一對卻直接隸屬于 Object 類,也就是說,所有對象都擁有這一對方法。初看起來這十分不可思議,但是實際上卻是很自然的,因為這一對方法阻塞時要釋放占用的鎖,而鎖是任何對象都具有的,調(diào)用任意對象的 wait() 方法導(dǎo)致線程阻塞,并且該對象上的鎖被釋放。而調(diào)用任意對象的notify()方法則導(dǎo)致因調(diào)用該對象的 wait()方法而阻塞的線程中隨機選擇的一個解除阻塞(但要等到獲得鎖后才真正可執(zhí)行)。
- 其次,前面敘述的所有方法都可在任何位置調(diào)用,但是wait() 和 notify() 方法這一對方法卻必須在 synchronized 方法或塊中調(diào)用,理由也很簡單,只有在synchronized方法或塊中當(dāng)前線程才占有鎖,才有鎖可以釋放。同樣的道理,調(diào)用這一對方法的對象上的鎖必須為當(dāng)前線程所擁有,這樣才有鎖可以釋放。因此,這一對方法調(diào)用必須放置在這樣的 synchronized方法或塊中,該方法或塊的上鎖對象就是調(diào)用這一對方法的對象。若不滿足這一條件,則程序雖然仍能編譯,但在運行時會出現(xiàn)IllegalMonitorStateException異常。
- wait() 和 notify()方法的上述特性決定了它們經(jīng)常和synchronized方法或塊一起使用,將它們和操作系統(tǒng)的進程間通信機制作一個比較就會發(fā)現(xiàn)它們的相似性:synchronized方法或塊提供了類似于操作系統(tǒng)原語的功能,它們的執(zhí)行不會受到多線程機制的干擾,而這一對方法則相當(dāng)于 block和wake up 原語(這一對方法均聲明為 synchronized)。它們的結(jié)合使得我們可以實現(xiàn)操作系統(tǒng)上一系列精妙的進程間通信的算法(如信號量算法),并用于解決各種復(fù)雜的線程間通信問題。
關(guān)于 wait() 和 notify() 方法最后再說明兩點:
- 第一:調(diào)用notify() 方法導(dǎo)致解除阻塞的線程是從因調(diào)用該對象的 wait()方法而阻塞的線程中隨機選取的,我們無法預(yù)料哪一個線程將會被選擇,所以編程時要特別小心,避免因這種不確定性而產(chǎn)生問題。
- 第二:除了notify(),還有一個方法 notifyAll()也可起到類似作用,唯一的區(qū)別在于,調(diào)用 notifyAll()方法將把因調(diào)用該對象的 wait()方法而阻塞的所有線程一次性全部解除阻塞。當(dāng)然,只有獲得鎖的那一個線程才能進入可執(zhí)行狀態(tài)。
談到阻塞,就不能不談一談死鎖,略一分析就能發(fā)現(xiàn),suspend()方法和不指定超時期限的wait()方法的調(diào)用都可能產(chǎn)生死鎖。遺憾的是,Java并不在語言級別上支持死鎖的避免,我們在編程中必須小心地避免死鎖