Java wait和notify虛假喚醒原理
自己在此記錄一下,方便日后復(fù)習(xí)。
虛假喚醒的概念
jdk官方文檔解釋:

所以說在wait和notify一塊使用時(shí),如果使用if作為條件時(shí),會(huì)有虛假喚醒的情況發(fā)生,所以必須使用while作為循環(huán)條件。下面來舉例實(shí)驗(yàn):
首先,創(chuàng)建一個(gè)資源類:(在多線程中,一般都是資源類和線程操作解耦,不放在用同一個(gè)類中,只有在線程操作資源類時(shí),才會(huì)創(chuàng)建資源類的對(duì)象)
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)滿的時(shí)候,進(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條件會(huì)發(fā)生什么:

對(duì),你沒看錯(cuò),怎么可能會(huì)出現(xiàn)負(fù)數(shù)呢,這肯定是不對(duì)的。冷靜下來分析一下,還是有點(diǎn)頭緒,知道哪里出現(xiàn)了問題的(那你是一個(gè)處事不驚的人,很厲害)。
來,分析一下,一開始先調(diào)用了消費(fèi)者C,D線程(因?yàn)槲覀儗懥怂咴谏a(chǎn)者中),消費(fèi)者此時(shí)發(fā)現(xiàn)此時(shí)product資源為0,所以,消費(fèi)者C,D這兩個(gè)兄弟,沒辦法只能調(diào)用wait方法,睡眠了,并且釋放了鎖。
但是此時(shí)第一個(gè)消費(fèi)者已經(jīng)蘇醒了,發(fā)動(dòng)機(jī)開始生產(chǎn)產(chǎn)品了,并且生產(chǎn)之后,又喚醒了所有等待的消費(fèi)者線程。這消費(fèi)者C,D兩兄弟終于蘇醒了,D哥們先獲得了鎖,所以就先消費(fèi)了一個(gè)產(chǎn)品,然后就又發(fā)現(xiàn)沒有產(chǎn)品了,又傷心的休眠去了,但是不要忘了,此時(shí)還有一個(gè)C哥們被喚醒了啊,你喚醒了人家,人家總的干點(diǎn)什么事情吧,不然這多難受,剛好不巧的是,此時(shí)的判斷條件是if,所以此時(shí)C哥們便不受條件的約束,接著上面自己睡眠的代碼處執(zhí)行,毅然決然的又去消費(fèi)了一個(gè)產(chǎn)品,原來D哥們消費(fèi)后,就已經(jīng)為0了,這個(gè)C哥們?cè)偃ハM(fèi)減一,不就是-1了嗎,以此類推分析。發(fā)現(xiàn)如果判斷條件用不好,此時(shí)喚醒的C哥們就相當(dāng)于虛假喚醒的了,會(huì)給程序帶來不可預(yù)估的錯(cuò)誤。所以在這里判斷必須要使用while,先來看看把if換成while的結(jié)果。

這回結(jié)果就比較正常了,為什么使用while就可以呢,因?yàn)橄裆衔乃f,即使喚醒了所有的消費(fèi)者線程,此時(shí)會(huì)不停while循環(huán)判斷,如果此時(shí)條件是為0,那么C哥們就不能出while,那么他也就不回執(zhí)行下面消費(fèi)產(chǎn)品的減減操作了,那么就會(huì)避免了這種錯(cuò)誤。這也是官方提倡的在使用wait 和notifyAll的時(shí)候,必須使用while循環(huán)條件判斷。
以上就是本文的全部內(nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Java觀察者模式之實(shí)現(xiàn)對(duì)象間的一對(duì)多依賴
這篇文章主要介紹了Java觀察者模式之實(shí)現(xiàn)對(duì)象間的一對(duì)多依賴的方法,Java觀察者模式是一種行為型設(shè)計(jì)模式,用于實(shí)現(xiàn)對(duì)象之間的消息傳遞和通信,文中有詳細(xì)的實(shí)現(xiàn)步驟和代碼示例,,需要的朋友可以參考下2023-05-05
springboot網(wǎng)站應(yīng)用使用第三方qq登錄的實(shí)現(xiàn)過程
這篇文章主要介紹了springboot網(wǎng)站應(yīng)用使用第三方qq登錄,本文通過實(shí)例圖文相結(jié)合給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-09-09
java實(shí)現(xiàn)發(fā)送短信驗(yàn)證碼
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)發(fā)送短信驗(yàn)證碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-07-07
詳解elasticsearch之metric聚合實(shí)現(xiàn)示例
這篇文章主要為大家介紹了elasticsearch之metric聚合實(shí)現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01
詳解java平臺(tái)解析協(xié)議相關(guān)備忘
這篇文章主要介紹了詳解java平臺(tái)解析協(xié)議相關(guān)備忘,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-01-01
SpringBoot中SmartLifecycle的使用解析
這篇文章主要介紹了SpringBoot中SmartLifecycle的使用解析,SmartLifecycle是一個(gè)擴(kuò)展了Lifecycle接口,可以跟蹤spring容器ApplicationContext刷新或者關(guān)閉的接口,實(shí)現(xiàn)該接口的實(shí)現(xiàn)類有特定的執(zhí)行順序,需要的朋友可以參考下2023-11-11
關(guān)于服務(wù)網(wǎng)關(guān)Spring Cloud Zuul(Finchley版本)
這篇文章主要介紹了關(guān)于服務(wù)網(wǎng)關(guān)Spring Cloud Zuul(Finchley版本),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03

