Java中wait()和notify()的正確使用方式
wait()、notify()、notifyAll() 是 Object 類 的原生方法,是 Java 最早提供的線程間協(xié)作機(jī)制(屬于低級別、重量級的等待/通知機(jī)制)。
核心規(guī)則(必須全部記住,否則 100% 會出問題)
必須在 synchronized 塊/方法中調(diào)用
- wait()、notify()、notifyAll() 都要求當(dāng)前線程持有同一個對象的監(jiān)視器鎖(monitor)
- 否則拋出
IllegalMonitorStateException
wait() 會釋放鎖
- 調(diào)用 wait() 后,當(dāng)前線程會釋放對象鎖,進(jìn)入該對象的等待隊(duì)列(wait set)
notify() / notifyAll() 不釋放鎖
- 只是喚醒等待隊(duì)列中的一個/全部線程,但不立即把鎖給被喚醒的線程
- 只有當(dāng)前持有鎖的線程離開 synchronized 塊后,被喚醒的線程才有機(jī)會競爭鎖
最經(jīng)典的寫法模板(生產(chǎn)級必須這樣寫)
// 消費(fèi)者
synchronized (lock) {
while (conditionNotMet) { // 必須用 while,不是 if?。ǚ乐固摷賳拘眩?
lock.wait(); // 釋放鎖并等待
}
// 條件滿足,消費(fèi)
doConsume();
}
// 生產(chǎn)者
synchronized (lock) {
// 生產(chǎn)
doProduce();
lock.notify(); // 或 notifyAll()
// 離開 synchronized 塊后,被喚醒的線程才有機(jī)會搶鎖
}
為什么必須用 while 而不是 if?(虛假喚醒經(jīng)典坑)
虛假喚醒(spurious wakeup):線程可能在沒有被 notify 的情況下被系統(tǒng)喚醒(極少見,但 JVM 規(guī)范允許)。
// 錯誤寫法(極易出問題)
if (queue.isEmpty()) {
lock.wait(); // 被虛假喚醒后,可能直接往下執(zhí)行,而隊(duì)列還是空的
}
// 正確寫法(生產(chǎn)環(huán)境唯一推薦)
while (queue.isEmpty()) {
lock.wait();
}
完整經(jīng)典示例:生產(chǎn)者-消費(fèi)者(固定大小隊(duì)列)
import java.util.LinkedList;
import java.util.Queue;
public class ProducerConsumer {
private final Queue<Integer> queue = new LinkedList<>();
private final int MAX_SIZE = 10;
private final Object lock = new Object();
class Producer implements Runnable {
@Override
public void run() {
try {
while (true) {
synchronized (lock) {
while (queue.size() == MAX_SIZE) {
System.out.println("隊(duì)列已滿,生產(chǎn)者等待...");
lock.wait();
}
int item = (int) (Math.random() * 100);
queue.offer(item);
System.out.println("生產(chǎn): " + item + ",當(dāng)前大小: " + queue.size());
lock.notifyAll(); // 喚醒所有等待的消費(fèi)者
}
Thread.sleep(500); // 模擬生產(chǎn)耗時
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
class Consumer implements Runnable {
@Override
public void run() {
try {
while (true) {
synchronized (lock) {
while (queue.isEmpty()) {
System.out.println("隊(duì)列為空,消費(fèi)者等待...");
lock.wait();
}
int item = queue.poll();
System.out.println("消費(fèi): " + item + ",當(dāng)前大小: " + queue.size());
lock.notifyAll(); // 喚醒可能等待的生產(chǎn)者
}
Thread.sleep(800); // 模擬消費(fèi)耗時
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
public static void main(String[] args) {
ProducerConsumer pc = new ProducerConsumer();
new Thread(pc.new Producer(), "生產(chǎn)者-1").start();
new Thread(pc.new Consumer(), "消費(fèi)者-1").start();
new Thread(pc.new Consumer(), "消費(fèi)者-2").start();
}
}
常見錯誤寫法匯總(你幾乎一定會踩)
| 錯誤寫法 | 后果 | 正確做法 |
|---|---|---|
| 在 synchronized 外面調(diào)用 wait() | IllegalMonitorStateException | 必須在 synchronized 內(nèi) |
| 用 if 判斷條件而不是 while | 虛假喚醒導(dǎo)致邏輯錯誤 | 永遠(yuǎn)用 while |
| 只用 notify() 而不用 notifyAll() | 可能導(dǎo)致部分線程永久等待(信號丟失) | 多消費(fèi)者/生產(chǎn)者場景用 notifyAll |
| notify() 后立即修改共享變量 | 可能導(dǎo)致被喚醒線程看到舊狀態(tài) | 修改完再 notify |
| 不同對象上 wait/notify | 線程永遠(yuǎn)喚不醒 | 必須用同一個鎖對象 |
2025–2026 年真實(shí)項(xiàng)目中的選擇建議
| 場景 | 推薦工具 | 原因 |
|---|---|---|
| 簡單生產(chǎn)者-消費(fèi)者、線程間狀態(tài)等待 | wait/notify | 輕量、無額外依賴 |
| 需要超時等待 | Condition.await(long, TimeUnit) | ReentrantLock 的 Condition 更靈活 |
| 大多數(shù)現(xiàn)代業(yè)務(wù)代碼 | BlockingQueue(ArrayBlockingQueue) | 封裝好了 wait/notify,API 更安全 |
| 高并發(fā)、復(fù)雜條件等待 | Condition + ReentrantLock | 支持多個等待隊(duì)列、公平鎖、可中斷 |
| 響應(yīng)式/異步場景 | CompletableFuture / reactor | 基本不再用原始 wait/notify |
一句話總結(jié)(面試/生產(chǎn)最常問的答案):
“wait() 和 notify() 必須在同一個對象的 synchronized 塊中使用,wait() 會釋放鎖并進(jìn)入等待隊(duì)列,notify() 只喚醒但不釋放鎖,永遠(yuǎn)用 while 判斷條件,多線程協(xié)作場景優(yōu)先考慮 notifyAll()。”
以上就是Java中wait()和notify()的正確使用方式的詳細(xì)內(nèi)容,更多關(guān)于Java使用wait()和notify()的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java中LinkedList的模擬實(shí)現(xiàn)
本文主要介紹了Java中LinkedList的模擬實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-06-06
IntelliJ IDEA中加速M(fèi)aven編譯的終極指南
這篇文章主要為大家詳細(xì)介紹了IntelliJ IDEA中加速M(fèi)aven編譯的終極指南,從 7 分鐘提速到 30 秒,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解下2025-10-10
Jmeter關(guān)聯(lián)實(shí)現(xiàn)及參數(shù)化使用解析
這篇文章主要介紹了Jmeter關(guān)聯(lián)實(shí)現(xiàn)及參數(shù)化使用解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-08-08
Springboot整合Netty實(shí)現(xiàn)RPC服務(wù)器的示例代碼
這篇文章主要介紹了Springboot整合Netty實(shí)現(xiàn)RPC服務(wù)器的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-01-01
Python如何使用@property @x.setter及@x.deleter
這篇文章主要介紹了Python如何使用@property @x.setter及@x.deleter,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-05-05
Java 使用Socket正確讀取數(shù)據(jù)姿勢
這篇文章主要介紹了Java 使用Socket正確讀取數(shù)據(jù)姿勢,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-10-10

