2020-05-11 淺談sleep、yield、wait、join的區(qū)別

在java程序里面資源可以分為cpu資源鎖資源兩類.
sleep(long mills): 讓出cpu資源,但是不會(huì)讓出鎖資源.
wait(): 讓出cpu資源和鎖資源.

1.Thread.sleep(long) 和Thread.yield()都是Thread類的靜態(tài)方法,在調(diào)用的時(shí)候都是Thread.sleep(long)/? Thread.yield()的方式進(jìn)行調(diào)用。而join()方法是線程對(duì)象來(lái)調(diào)用.
2.wait()和notify(),notifyAll()這三個(gè)方法都是object的方法!
object是所有java對(duì)象的超類,都會(huì)實(shí)現(xiàn)object的方法:
Object參考:Java超類-java.lang.object.

它們都是用于協(xié)調(diào)多個(gè)線程對(duì)共享數(shù)據(jù)的存取,所以必須在Synchronized語(yǔ)句塊內(nèi)使用這三個(gè)方法。前面說(shuō)過(guò)Synchronized這個(gè)關(guān)鍵字用于保護(hù)共享數(shù)據(jù),阻止其他線程對(duì)共享數(shù)據(jù)的存取。但是這樣程序的流程就很不靈活了,如何才能在當(dāng)前線程還沒(méi)退出Synchronized數(shù)據(jù)塊時(shí)讓其他線程也有機(jī)會(huì)訪問(wèn)共享數(shù)據(jù)呢?此時(shí)就用這三個(gè)方法來(lái)靈活控制。
(1) wait()方法使當(dāng)前線程暫停執(zhí)行并釋放對(duì)象鎖標(biāo)志,讓其他線程可以進(jìn)入Synchronized數(shù)據(jù)塊,當(dāng)前線程被放入對(duì)象等待池中。(2) 當(dāng)調(diào)用 notify()方法后,將從對(duì)象的等待池中移走一個(gè)任意的線程并放到鎖標(biāo)志等待池中,只有鎖標(biāo)志等待池中的線程能夠獲取鎖標(biāo)志;如果鎖標(biāo)志等待池中沒(méi)有線程,則notify()不起作用。
(3)?notifyAll()則從對(duì)象等待池中移走所有等待那個(gè)對(duì)象的線程并放到鎖標(biāo)志等待池中.

sleep與wait的區(qū)別:sleep是線程方法,wait是object方法;看區(qū)別,主要是cpu的運(yùn)行機(jī)制:

這兩個(gè)方法主要要考慮的是: 1. cpu是否繼續(xù)運(yùn)行,2.鎖資源是否釋放.
對(duì)于這兩點(diǎn),首先需要弄清楚電腦運(yùn)行的機(jī)制:cpu的運(yùn)行實(shí)際是為每一個(gè)線程分時(shí)間片去執(zhí)行,每個(gè)時(shí)間片都很短,cpu不停地切換不同的線程,以看似他們同步在運(yùn)行.

sleep?,釋放cpu資源,不釋放鎖資源,如果線程進(jìn)入sleep的話,釋放cpu資源,如果外層包有Synchronize,那么此鎖并沒(méi)有釋放掉。wait,釋放cpu資源,也釋放鎖資源,一般用于鎖機(jī)制中?肯定是要釋放掉鎖的,因?yàn)閚otify并不會(huì)立即調(diào)起此線程,因此cpu是不會(huì)為其分配時(shí)間片的,也就是說(shuō)wait 線程進(jìn)入等待池,cpu不分時(shí)間片給它,鎖釋放掉。(wait用于鎖機(jī)制,sleep不是,這就是為啥sleep不釋放鎖,wait釋放鎖的原因,sleep是線程的方法,跟鎖沒(méi)半毛錢關(guān)系,wait,notify,notifyall 都是Object對(duì)象的方法,是一起使用的,用于鎖機(jī)制)

總結(jié):

1. sleep:Thread類的方法,必須帶一個(gè)時(shí)間參數(shù).會(huì)讓當(dāng)前線程休眠進(jìn)入阻塞狀態(tài)并釋放cpu,但是sleep不會(huì)釋放鎖資源,(Sleep釋放CPU,wait 也會(huì)釋放cpu,因?yàn)閏pu資源太寶貴了,只有在線程running的時(shí)候,才會(huì)獲取cpu片段),提供其他線程運(yùn)行的機(jī)會(huì)且不考慮優(yōu)先級(jí),但如果有同步鎖則sleep不會(huì)釋放鎖即其他線程無(wú)法獲得同步鎖??可通過(guò)調(diào)用interrupt()方法來(lái)喚醒休眠線程。

2.yield:讓出CPU調(diào)度,Thread類的方法,類似sleep只是不能由用戶指定暫停多長(zhǎng)時(shí)間 ,并且yield()方法只能讓同優(yōu)先級(jí)的線程有執(zhí)行的機(jī)會(huì)。?yield()只是使當(dāng)前線程重新回到可執(zhí)行狀態(tài),所以執(zhí)行yield()的線程有可能在進(jìn)入到可執(zhí)行狀態(tài)后馬上又被執(zhí)行。調(diào)用yield方法只是一個(gè)建議,告訴線程調(diào)度器我的工作已經(jīng)做的差不多了,可以讓別的相同優(yōu)先級(jí)的線程使用CPU了,沒(méi)有任何機(jī)制保證采納。

3.wait:Object類的方法(notify()、notifyAll() ?也是Object對(duì)象),必須放在循環(huán)體和同步代碼塊中,執(zhí)行該方法的線程會(huì)釋放鎖,進(jìn)入線程等待池中等待被再次喚醒(notify隨機(jī)喚醒,notifyAll全部喚醒,線程結(jié)束自動(dòng)喚醒)即放入鎖池中競(jìng)爭(zhēng)同步鎖

4.join:一種特殊的wait,當(dāng)前運(yùn)行線程調(diào)用另一個(gè)線程的join方法,當(dāng)前線程進(jìn)入阻塞狀態(tài)直到另一個(gè)線程運(yùn)行結(jié)束等待該線程終止。?注意該方法也需要捕捉異常。

等待調(diào)用join方法的線程結(jié)束,再繼續(xù)執(zhí)行。如:t.join();//主要用于等待t線程運(yùn)行結(jié)束,若無(wú)此句,main則會(huì)執(zhí)行完畢,導(dǎo)致結(jié)果不可預(yù)測(cè)

關(guān)于Java中線程的生命周期,首先看一下下面這張較為經(jīng)典的圖:


上圖中基本上囊括了Java中多線程各重要知識(shí)點(diǎn)。掌握了上圖中的各知識(shí)點(diǎn),Java中的多線程也就基本上掌握了。主要包括:

線程的5個(gè)狀態(tài)

1、新建狀態(tài)(New):當(dāng)線程對(duì)象對(duì)創(chuàng)建后,即進(jìn)入了新建狀態(tài),如:Thread t = new MyThread();

2、就緒狀態(tài)(Runnable):當(dāng)調(diào)用線程對(duì)象的start()方法(t.start();),線程即進(jìn)入就緒狀態(tài)。處于就緒狀態(tài)的線程,只是說(shuō)明此線程已經(jīng)做好了準(zhǔn)備,隨時(shí)等待CPU調(diào)度執(zhí)行,獲取cpu 的使用權(quán),并不是說(shuō)執(zhí)行了t.start()此線程立即就會(huì)執(zhí)行

3、運(yùn)行狀態(tài)(Running):可運(yùn)行狀態(tài)(runnable)的線程獲得了cpu 時(shí)間片(timeslice) ,執(zhí)行程序代碼。 當(dāng)CPU開(kāi)始調(diào)度處于就緒狀態(tài)的線程時(shí),此時(shí)線程才得以真正執(zhí)行,即進(jìn)入到運(yùn)行狀態(tài)。注:就 緒狀態(tài)是進(jìn)入到運(yùn)行狀態(tài)的唯一入口,也就是說(shuō),線程要想進(jìn)入運(yùn)行狀態(tài)執(zhí)行,首先必須處于就緒狀態(tài)中;

4、阻塞狀態(tài)(Blocked):處于運(yùn)行狀態(tài)中的線程由于某種原因,暫時(shí)放棄對(duì)CPU的使用權(quán),,也即讓出了cpu timeslice, 停止執(zhí)行,此時(shí)進(jìn)入阻塞狀態(tài),直到其進(jìn)入到就緒狀態(tài),才 有機(jī)會(huì)再次被CPU調(diào)用以進(jìn)入到運(yùn)行狀態(tài)。才有機(jī)會(huì)再次獲得cpu timeslice 轉(zhuǎn)到運(yùn)行(running)狀態(tài) 根據(jù)阻塞產(chǎn)生的原因不同,阻塞狀態(tài)又可以分為三種:


1.等待阻塞:運(yùn)行狀態(tài)中的線程執(zhí)行wait()方法,使本線程進(jìn)入到等待阻塞狀態(tài);,JVM會(huì)把該線程放入等待隊(duì)列(waitting queue)中。

2.同步阻塞 –,運(yùn)行(running)的線程在獲取對(duì)象的同步鎖時(shí),若該同步鎖被別的線程占用,獲取synchronized同步鎖失敗 ,?它會(huì)進(jìn)入同步阻塞狀態(tài) ,則JVM會(huì)把該線程放入鎖池(lock pool)中。

3.其他阻塞 – 通過(guò)調(diào)用線程的sleep()或join()或發(fā)出了I/O請(qǐng)求時(shí),線程會(huì)進(jìn)入到阻塞狀態(tài)。當(dāng)sleep()狀態(tài)超時(shí)、join()等待線程終止或者超時(shí)、或者I/O處理完畢時(shí),線程重新轉(zhuǎn)入就緒狀態(tài)。運(yùn)行(running)的線程執(zhí)行Thread.sleep(long ms)或t.join()方法,或者發(fā)出了I/O請(qǐng)求時(shí),JVM會(huì)把該線程置為阻塞狀態(tài)。當(dāng)sleep()狀態(tài)超時(shí)、join()等待線程終止或者超時(shí)、或者I/O處理完畢時(shí),線程重新轉(zhuǎn)入可運(yùn)行(runnable)狀態(tài)。

5、死亡狀態(tài)(Dead):線程執(zhí)行完了或者因異常退出了run()方法,該線程結(jié)束生命周期。

從圖中可以看出,只有runnable到running時(shí)才會(huì)占用cpu時(shí)間片,其他都會(huì)出讓cpu時(shí)間片。

線程的資源有不少,但應(yīng)該包含CPU資源和鎖資源這兩類。

sleep(long mills):讓出CPU資源,但是不會(huì)釋放鎖資源。

wait():讓出CPU資源和鎖資源。

鎖是用來(lái)線程同步的,sleep(long mills)雖然讓出了CPU,但是不會(huì)讓出鎖,其他線程可以利用CPU時(shí)間片了,但如果其他線程要獲取sleep(long mills)擁有的鎖才能執(zhí)行,則會(huì)因?yàn)闊o(wú)法獲取鎖而不能執(zhí)行,繼續(xù)等待。

但是那些沒(méi)有和sleep(long mills)競(jìng)爭(zhēng)鎖的線程,一旦得到CPU時(shí)間片即可運(yùn)行了。

5.?死亡(DEAD):線程run()、main() 方法執(zhí)行結(jié)束,或者因異常退出了run()方法,則該線程結(jié)束生命周期。死亡的線程不可再次復(fù)生。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容