詳解Java程序并發(fā)的Wait-Notify機(jī)制
Wait-Notify場景
典型的Wait-Notify場景一般與以下兩個(gè)內(nèi)容相關(guān):
1. 狀態(tài)變量(State Variable)
當(dāng)線程需要wait的時(shí)候,總是因?yàn)橐恍l件得不到滿足導(dǎo)致的。例如往隊(duì)列里填充數(shù)據(jù),當(dāng)隊(duì)列元素已經(jīng)滿時(shí),線程就需要wait停止運(yùn)行。當(dāng)隊(duì)列元素有空缺時(shí),再繼續(xù)自己的執(zhí)行。
2. 條件斷言(Condition Predicate)
當(dāng)線程確定是否進(jìn)入wait或者是從notify醒來的時(shí)候是否繼續(xù)往下執(zhí)行,大部分都要測試狀態(tài)條件是否滿足。例如,往隊(duì)列里添加元素,隊(duì)列已滿,于是阻塞當(dāng)前線程,當(dāng)有其他線程從隊(duì)列里取走了元素,就通知在等待的線程“隊(duì)列有剩余空間,可以往里添加元素了”。這時(shí),等待添加元素的進(jìn)程就會(huì)被喚醒,然后判斷一下當(dāng)前隊(duì)列是否真的有剩余空間,如果真的有剩余空間,就將元素添加進(jìn)去,如果沒有,則繼續(xù)阻塞等待下次喚醒。
3. 條件隊(duì)列(Condition Queue)
每個(gè)對象都有一個(gè)內(nèi)置的條件隊(duì)列,當(dāng)一個(gè)線程在該對象鎖上調(diào)用wait函數(shù)的時(shí)候,就會(huì)將該線程加入到該對象的條件隊(duì)列中。
注意
wait與notify是Java同步機(jī)制中的重要組成部分。結(jié)合與synchronized關(guān)鍵字使用,可以建立很多優(yōu)秀的同步模型,例如生產(chǎn)者-消費(fèi)者模型。但是在使用wait()、notify()、notifyAll()函數(shù)的時(shí)候,需要特別注意以下幾點(diǎn):
wait()、notify()、notifyAll()方法不屬于Thread類,而是屬于Object基礎(chǔ)類,也就是說每個(gè)對象都有wait()、notify()、notifyAll()的功能。因?yàn)槊總€(gè)對象都有鎖,鎖是每個(gè)對象的基礎(chǔ),因此操作鎖的方法也是最基礎(chǔ)的。
調(diào)用obj的wait(), notify()方法前,必須獲得obj鎖,也就是必須寫在synchronized(obj){...} 代碼段內(nèi)。
調(diào)用obj.wait()后,線程A就釋放了obj的鎖,否則線程B無法獲得obj鎖,也就無法在synchronized(obj){...} 代碼段內(nèi)喚醒線程A。
當(dāng)obj.wait()方法返回后,線程A需要再次獲得obj鎖,才能繼續(xù)執(zhí)行。
如果線程A1,A2,A3都在obj.wait(),則線程B調(diào)用obj.notify()只能喚醒線程A1,A2,A3中的一個(gè)(具體哪一個(gè)由JVM決定)。
如果線程B調(diào)用obj.notifyAll()則能全部喚醒等待的線程A1,A2,A3,但是等待的線程要繼續(xù)執(zhí)行obj.wait()的下一條語句,必須獲得obj鎖。因此,線程A1,A2,A3只有一個(gè)有機(jī)會(huì)獲得鎖繼續(xù)執(zhí)行,例如A1,其余的需要等待A1釋放obj鎖之后才能繼續(xù)執(zhí)行。
當(dāng)線程B調(diào)用obj.notify()或者obj.notifyAll()的時(shí)候,線程B正持有obj鎖,因此,線程A1,A2,A3雖被喚醒,但是仍無法獲得obj鎖。直到線程B退出synchronized代碼塊,釋放obj鎖后,線程A1,A2,A3中的一個(gè)才有機(jī)會(huì)獲得對象鎖并得以繼續(xù)執(zhí)行。
示例代碼
線程的wait操作的典型代碼結(jié)構(gòu)如下:
public void test() throws InterruptedException { synchronized(obj) { while (! contidition) { obj.wait(); } } }
為什么obj.wait()操作必須位于循環(huán)中呢?有以下幾個(gè)主要原因:
1. 一個(gè)對象鎖可能用于保護(hù)多個(gè)狀態(tài)變量,當(dāng)它們都需要wait-notify操作時(shí),如果不將wait放到while循環(huán)中就會(huì)有問題。例如,某對象鎖obj保護(hù)兩種狀態(tài)變量a和b,當(dāng)a的條件斷言不成立時(shí)發(fā)生了wait操作,當(dāng)b的條件斷言不成立時(shí)也發(fā)生了wait操作,兩個(gè)線程被加入到obj對應(yīng)的條件隊(duì)列中?,F(xiàn)在若改變狀態(tài)變量a的某操作發(fā)生,在obj上調(diào)用了notifyAll操作,則obj對應(yīng)的條件隊(duì)列里的所有線程均被喚醒,之前等待a的一個(gè)或幾個(gè)線程去判斷a的條件斷言可能成立了,但是b對于的條件斷言肯定仍不成立,而此時(shí)等待b的線程也被喚醒了,所以需要循環(huán)判斷b的條件斷言是否滿足,如果不滿足,則繼續(xù)wait。
2. 多個(gè)線程wait的同一狀態(tài)的條件斷言。例如,向隊(duì)列添加元素的場景,當(dāng)前隊(duì)列是滿的,多個(gè)線程想往里面添加元素,于是都wait了。此時(shí),另一個(gè)線程從隊(duì)列里取出了一個(gè)元素,調(diào)用了notifyAll操作,喚醒了所有線程,但是只有一個(gè)線程能夠往隊(duì)列里添加一個(gè)元素,其他的仍需要等待。
3. 虛假喚醒。在沒有被通知、中斷或超時(shí)的情況下,線程自動(dòng)蘇醒了。雖然這種情況在實(shí)踐中很少發(fā)生,但是通過循環(huán)等待可以杜絕這一情況的發(fā)生。
相關(guān)文章
Java如何導(dǎo)入Jsoup庫做一個(gè)有趣的爬蟲項(xiàng)目
Jsoup庫是一款Java的HTML解析器,可用于從網(wǎng)絡(luò)或本地文件中獲取HTML文檔并解析其中的數(shù)據(jù),這篇文章給大家介紹Java導(dǎo)入Jsoup庫做一個(gè)有趣的爬蟲項(xiàng)目,感興趣的朋友跟隨小編一起看看吧2023-11-11利用Postman和Chrome的開發(fā)者功能探究項(xiàng)目(畢業(yè)設(shè)計(jì)項(xiàng)目)
這篇文章主要介紹了利用Postman和Chrome的開發(fā)者功能探究項(xiàng)目(畢業(yè)設(shè)計(jì)項(xiàng)目),本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-12-12SpringBoot ThreadLocal實(shí)現(xiàn)公共字段自動(dòng)填充案例講解
每一次在Controller層中封裝改動(dòng)數(shù)據(jù)的方法時(shí)都要重新設(shè)置一些共性字段,顯得十分冗余。為了解決此問題也是在項(xiàng)目中第一次利用到線程,總的來說還是讓我眼前一亮,也開闊了視野,對以后的開發(fā)具有深遠(yuǎn)的意義2022-10-10java后臺(tái)實(shí)現(xiàn)支付寶支付接口和支付寶訂單查詢接口(前端為APP)
這篇文章主要介紹了java后臺(tái)實(shí)現(xiàn)支付寶支付接口和支付寶訂單查詢接口(前端為APP),非常具有實(shí)用價(jià)值,需要的朋友可以參考下2018-08-08