徹底理解Java線程通信wait?/?notify(原理?+?實(shí)戰(zhàn))
前言
在 Java 多線程開發(fā)中,wait()、notify() 和 notifyAll() 是最經(jīng)典但也最容易寫錯(cuò)的一套線程通信機(jī)制。
它們用于解決:當(dāng)線程因?yàn)?ldquo;條件不滿足”無法繼續(xù)執(zhí)行時(shí),如何安全地等待并在條件改變后繼續(xù)執(zhí)行。
這篇文章將從原理、流程、示例到最佳實(shí)踐,把這三兄弟一次講透。
一、為什么需要 wait / notify?(最根本的問題)
在多線程場景中,經(jīng)常會(huì)出現(xiàn):
- 消費(fèi)者想消費(fèi),但倉庫空了
- 生產(chǎn)者想生產(chǎn),但倉庫滿了
- 主線程需要等待子線程準(zhǔn)備好某個(gè)結(jié)果
- 條件未滿足前,線程無法繼續(xù)往下執(zhí)行
如果用 while(true) 死循環(huán)檢查:
while (item == 0) {
// 忙等(Busy waiting),瘋狂消耗 CPU
}這會(huì)導(dǎo)致:
- CPU 空轉(zhuǎn)
- 性能極差
- 線程爭搶激烈
于是,Java 提供了 基于鎖對象的條件等待機(jī)制:
當(dāng)條件不滿足時(shí),線程主動(dòng)掛起自己,并釋放鎖;當(dāng)條件滿足時(shí),被其他線程喚醒繼續(xù)執(zhí)行。
這就是 wait / notify。
二、wait / notify 的正確理解(核心概念)
1. wait() 的本質(zhì)動(dòng)作
當(dāng)線程執(zhí)行:
lock.wait();
它會(huì)做 三件事:
- 當(dāng)前線程暫停執(zhí)行(掛起)
- 釋放 lock 的那把鎖(讓其他線程有機(jī)會(huì)修改狀態(tài))
- 把自己加入 lock 對象的“等待隊(duì)列(Wait Set)”
線程會(huì)一直睡在那里,直到某個(gè)線程執(zhí)行了:
lock.notify()或lock.notifyAll()
并且:
- ?? 喚醒的線程必須重新?lián)?lock 的鎖
- 搶到鎖之后,才能繼續(xù)執(zhí)行 wait 后面的代碼
2. notify() / notifyAll() 做了什么?
兩個(gè)方法都必須在同步代碼塊中調(diào)用:
synchronized(lock) {
lock.notify();
}否則會(huì)拋:
IllegalMonitorStateException
notify() 作用:
從 lock 的等待隊(duì)列中 隨機(jī)喚醒 1 個(gè)線程
但喚醒 ≠ 立即執(zhí)行
被喚醒線程要在當(dāng)前線程釋放鎖后才能搶鎖執(zhí)行
notifyAll() 作用:
喚醒等待隊(duì)列中的所有線程
所有線程一起去搶鎖,成功者繼續(xù)執(zhí)行
實(shí)際開發(fā)中一般推薦 notifyAll():更安全,避免喚醒錯(cuò)誤線程導(dǎo)致死鎖。
三、完整流程圖解(非常關(guān)鍵)
假設(shè)線程 A 想消費(fèi)商品,但倉庫空了:
線程 A 做的事:
synchronized(lock)→ 拿到鎖判斷倉庫是否為空 → 是空的
調(diào)用
wait():A 掛起
A 釋放鎖
A 進(jìn)入等待隊(duì)列
線程 B(生產(chǎn)者)做的事:
synchronized(lock)→ 拿到鎖生產(chǎn)商品
調(diào)用
notify()或notifyAll():喚醒 AB 退出 synchronized → 釋放鎖
最后:
A 被喚醒
搶到鎖后,繼續(xù)執(zhí)行 wait() 下一行的代碼
四、生產(chǎn)者消費(fèi)者完整 Demo(最經(jīng)典示例)
class Depot {
private int item = 0;
private final Object lock = new Object();
public void produce() throws InterruptedException {
synchronized (lock) {
while (item == 1) { // 倉庫滿 → 等待
lock.wait();
}
item = 1;
System.out.println("生產(chǎn)了一個(gè)商品");
lock.notifyAll(); // 通知消費(fèi)者
}
}
public void consume() throws InterruptedException {
synchronized (lock) {
while (item == 0) { // 倉庫空 → 等待
lock.wait();
}
item = 0;
System.out.println("消費(fèi)了一個(gè)商品");
lock.notifyAll(); // 通知生產(chǎn)者
}
}
}為什么要用while而不是 if?
因?yàn)椋?/p>
Java 的 wait 存在 虛假喚醒(Spurious Wakeups)
被喚醒后必須重新判斷條件
這是 JDK 官方文檔明確要求的。
五、wait 和 sleep 有什么區(qū)別?(面試必考)
| 特性 | wait | sleep |
|---|---|---|
| 屬于誰? | Object | Thread |
| 是否釋放鎖? | 釋放 | 不釋放 |
| 是否必須在 synchronized 中? | 必須 | 不需要 |
| 喚醒方式 | notify/notifyAll | 到點(diǎn)自動(dòng)醒 |
一句話總結(jié):
sleep 是讓線程睡覺
wait 是讓線程到等待隊(duì)列里等別人叫醒
六、常見錯(cuò)誤理解(90% 的人會(huì)踩坑)
1. 認(rèn)為 notify 會(huì)立即讓對方執(zhí)行
錯(cuò)誤!
notify 只是“叫醒”,對方必須等待 當(dāng)前線程釋放鎖 后才能執(zhí)行。
2. 不用 while,用 if 判斷條件
會(huì)導(dǎo)致喚醒后條件不滿足卻繼續(xù)執(zhí)行 → 出 Bug
3. 以為 wait 是“做完事情后休息”
完全相反!
wait 是因?yàn)樽霾幌氯ィ皇且驗(yàn)樽鐾炅恕?/strong>
4. wait 和 notify 不在同一個(gè)鎖對象上
比如:
a.wait(); b.notify();
永遠(yuǎn)喚不醒。
七、總結(jié)(建議背下來)
wait 的本質(zhì):
線程因?yàn)闂l件不滿足 → 掛起自己 → 釋放鎖 → 進(jìn)入等待隊(duì)列 → 等別人喚醒。
notify / notifyAll 的本質(zhì):
條件改變 → 喚醒等待隊(duì)列里的線程,讓它們重新?lián)屾i繼續(xù)執(zhí)行。
wait / notify 是同步代碼里的“條件等待機(jī)制”,不是休眠機(jī)制。
八、這套機(jī)制與 RxJava 背壓有什么關(guān)系?
其實(shí)本質(zhì)一樣:
RxJava 背壓:下游忙 → 上游暫停、等待或限速
wait/notify:條件不滿足 → 當(dāng)前線程暫停等待其他線程改變條件
都是:
生產(chǎn)速度與消費(fèi)速度不一致時(shí)的流量控制(Flow Control)思想。
總結(jié)
到此這篇關(guān)于Java線程通信wait/notify的文章就介紹到這了,更多相關(guān)Java線程通信wait/notify內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Java通過notify和wait實(shí)現(xiàn)線程間的通信功能
- 淺談線程通信wait,notify作用
- Java多線程通信wait()和notify()代碼實(shí)例
- java使用wait和notify實(shí)現(xiàn)線程通信
- 淺談Java線程間通信之wait/notify
- Java線程通信之wait-notify通信方式詳解
- Java通過wait()和notifyAll()方法實(shí)現(xiàn)線程間通信
- Java多線程中的wait/notify通信模式實(shí)例詳解
- Java使用wait和notify實(shí)現(xiàn)線程之間的通信
- Java使用wait/notify實(shí)現(xiàn)線程間通信上篇
相關(guān)文章
Java關(guān)于BeabUtils.copyproperties的用法
這篇文章主要介紹了Java關(guān)于BeabUtils.copyproperties的用法,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-08-08
SpringBoot集成Mybatis-Plus多租戶架構(gòu)實(shí)現(xiàn)
本文主要介紹了SpringBoot集成Mybatis-Plus多租戶架構(gòu)實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-09-09
MyBatis動(dòng)態(tài)<if>標(biāo)簽使用避坑指南
這篇文章主要為大家介紹了MyBatis動(dòng)態(tài)<if>標(biāo)簽使用避坑指南,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03
Java設(shè)計(jì)模式之觀察者模式_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
這篇文章給大家介紹流量java設(shè)計(jì)模式之觀察者模式,定義對象間一種一對多的依賴關(guān)系,使得當(dāng)每一個(gè)對象改變狀態(tài)。下面通過類圖和實(shí)例代碼給大家介紹java設(shè)計(jì)模式之觀察者模式,感興趣的朋友一起看看吧2017-08-08
Spring Core核心類庫的功能與應(yīng)用實(shí)踐分析
本文詳細(xì)介紹了SpringCore核心類庫的功能、應(yīng)用實(shí)踐和底層原理,SpringCore提供了控制反轉(zhuǎn)(IOC)、依賴注入(DI)、Bean管理以及JNDI、定時(shí)任務(wù)等企業(yè)級(jí)功能,文章通過多個(gè)Java示例展示了SpringCore的應(yīng)用,感興趣的朋友跟隨小編一起看看吧2024-12-12
啟用設(shè)置org.slf4j.Logger打印并輸出日志方式
這篇文章主要介紹了啟用設(shè)置org.slf4j.Logger打印并輸出日志方式,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-11-11
java使用短信設(shè)備發(fā)送sms短信的示例(java發(fā)送短信)
這篇文章主要介紹了java使用短信設(shè)備發(fā)送sms短信的示例(java發(fā)送短信),需要的朋友可以參考下2014-04-04

