Java wait和notify虛假喚醒原理
自己在此記錄一下,方便日后復(fù)習(xí)。
虛假喚醒的概念
jdk官方文檔解釋:
所以說在wait和notify一塊使用時,如果使用if作為條件時,會有虛假喚醒的情況發(fā)生,所以必須使用while作為循環(huán)條件。下面來舉例實(shí)驗(yàn):
首先,創(chuàng)建一個資源類:(在多線程中,一般都是資源類和線程操作解耦,不放在用同一個類中,只有在線程操作資源類時,才會創(chuàng)建資源類的對象)
package com.test;/** * 資源類 * @author Huxudong * @createTime 2020-04-01 21:57:39 **/public class Resource { /** 產(chǎn)品數(shù) */ private int product = 0; /** 進(jìn)貨 */ public synchronized void get() { if(product >= 10) { System.out.println(Thread.currentThread().getName()+':'+'產(chǎn)品已滿!'); /** 當(dāng)商品已經(jīng)滿的時候,進(jìn)貨線程掛起 */ try {this.wait(); } catch (InterruptedException e) {e.printStackTrace(); } } /** 進(jìn)貨 */ System.out.println(Thread.currentThread().getName()+':'+ ++product); /** 喚醒其他線程 */ this.notifyAll(); } /** 售貨 */ public synchronized void sale() { if(product <= 0) { System.out.println(Thread.currentThread().getName()+':'+'產(chǎn)品已空'); try {this.wait(); } catch (InterruptedException e) {e.printStackTrace(); } } /** 售貨 */ System.out.println(Thread.currentThread().getName()+':'+ --product); /** 喚醒其他線程 */ this.notify(); }}
然后再創(chuàng)建線程來操作我們的資源類(通過java8新特性Lambda表達(dá)式直接創(chuàng)建)
package com.test;import java.util.concurrent.TimeUnit;/** * 線程操作資源類,實(shí)現(xiàn)線程與資源類的解耦合 * @author Huxudong * @createTime 2020-04-01 23:13:54 **/public class TestPc { public static void main(String[] args) { Resource resource = new Resource(); new Thread(()->{ for (int i = 0; i < 20; i++) {try { /** 睡眠,便于觀察結(jié)果 */ TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) { e.printStackTrace();}resource.get(); } },'生產(chǎn)者A').start(); new Thread(()->{ for (int i = 0; i < 20; i++) {resource.sale(); } },'消費(fèi)者C').start(); new Thread(()->{ for (int i = 0; i < 20; i++) {resource.get(); } },'生產(chǎn)者B').start(); new Thread(()->{ for (int i = 0; i < 20; i++) {resource.sale(); } },'消費(fèi)者D').start(); }}
先來看看如果使用if條件會發(fā)生什么:
對,你沒看錯,怎么可能會出現(xiàn)負(fù)數(shù)呢,這肯定是不對的。冷靜下來分析一下,還是有點(diǎn)頭緒,知道哪里出現(xiàn)了問題的(那你是一個處事不驚的人,很厲害)。
來,分析一下,一開始先調(diào)用了消費(fèi)者C,D線程(因?yàn)槲覀儗懥怂咴谏a(chǎn)者中),消費(fèi)者此時發(fā)現(xiàn)此時product資源為0,所以,消費(fèi)者C,D這兩個兄弟,沒辦法只能調(diào)用wait方法,睡眠了,并且釋放了鎖。
但是此時第一個消費(fèi)者已經(jīng)蘇醒了,發(fā)動機(jī)開始生產(chǎn)產(chǎn)品了,并且生產(chǎn)之后,又喚醒了所有等待的消費(fèi)者線程。這消費(fèi)者C,D兩兄弟終于蘇醒了,D哥們先獲得了鎖,所以就先消費(fèi)了一個產(chǎn)品,然后就又發(fā)現(xiàn)沒有產(chǎn)品了,又傷心的休眠去了,但是不要忘了,此時還有一個C哥們被喚醒了啊,你喚醒了人家,人家總的干點(diǎn)什么事情吧,不然這多難受,剛好不巧的是,此時的判斷條件是if,所以此時C哥們便不受條件的約束,接著上面自己睡眠的代碼處執(zhí)行,毅然決然的又去消費(fèi)了一個產(chǎn)品,原來D哥們消費(fèi)后,就已經(jīng)為0了,這個C哥們再去消費(fèi)減一,不就是-1了嗎,以此類推分析。發(fā)現(xiàn)如果判斷條件用不好,此時喚醒的C哥們就相當(dāng)于虛假喚醒的了,會給程序帶來不可預(yù)估的錯誤。所以在這里判斷必須要使用while,先來看看把if換成while的結(jié)果。
這回結(jié)果就比較正常了,為什么使用while就可以呢,因?yàn)橄裆衔乃f,即使喚醒了所有的消費(fèi)者線程,此時會不停while循環(huán)判斷,如果此時條件是為0,那么C哥們就不能出while,那么他也就不回執(zhí)行下面消費(fèi)產(chǎn)品的減減操作了,那么就會避免了這種錯誤。這也是官方提倡的在使用wait 和notifyAll的時候,必須使用while循環(huán)條件判斷。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持好吧啦網(wǎng)。
相關(guān)文章:
