wait()、notify()、notifyAll() 是 Java 線程間通信的基石,但也是並發 Bug 的高發區。本文通過源碼級語義分析、實戰代碼演示、輸出對比,深度揭示三者差異、喚醒機制、虛假喚醒處理及生產環境最佳實踐。
| 方法 | 所屬類 | 前置條件 | 語義 | 鎖行為 |
|---|---|---|---|---|
wait() | Object | 持有對象監視器鎖 | 釋放鎖,進入等待集,阻塞直到被喚醒或超時 | 釋放當前對象鎖 |
notify() | Object | 持有對象監視器鎖 | 隨機喚醒等待集中一個線程 | 不釋放鎖,退出同步塊後釋放 |
notifyAll() | Object | 持有對象監視器鎖 | 喚醒等待集中所有線程 | 不釋放鎖,退出同步塊後釋放 |
每個對象關聯兩個隊列:
wait() 釋放鎖後進入的集合。notify/notifyAll 將線程從等待集移至同步隊列,線程需重新競爭鎖才能繼續執行。
JVM 規範允許線程在無 notify、無中斷、無超時情況下自發醒來(虛假喚醒)。必須在循環中調用 wait(),重新檢查條件:
synchronized (lock) {
while (!condition) { // 必須用 while,不可用 if
lock.wait();
}
// 條件滿足,執行業務邏輯
}
場景:一個緩衝區,多生產者、多消費者。若使用 notify():
notify(),可能喚醒另一個生產者(緩衝區已滿),導致其再次 wait(),真正的消費者永不被喚醒 → 信號丟失,系統假死。notifyAll() 喚醒所有,消費者檢查條件消費,生產者檢查條件繼續等待,正確推進。原則:除非確定等待集中所有線程等待同一條件、且每次喚醒只需一個線程響應(如線程池任務分發),否則一律使用 notifyAll()。
public class WaitNotifyDemo {
private int counter = 0;
private final String name;
public WaitNotifyDemo(int counter, String name) {
this.counter = counter;
this.name = name;
}
public synchronized void doSomething() {
int temp = --counter;
if (temp <= 0) {
customizedNotifyAll(); // 或 customizedNotify()
} else {
while (temp > 0) {
try {
System.out.println(Thread.currentThread().getName() + "-<" + name + temp + "> will invoke WAIT()");
--temp;
wait();
} catch (InterruptedException e) {
e.printStackTrace();
notifyAll(); // 中斷時保護性喚醒
}
System.out.println(Thread.currentThread().getName() + "-<" + name + temp + "> has been ACTIVED");
}
customizedNotifyAll();
}
}
public void customizedNotifyAll() {
notifyAll(); // 此處可替換為 notify() 觀察差異
System.out.println(Thread.currentThread().getName() + "-<" + name + counter + ">::INVOKED NOTIFYALL() AND FINISHED");
}
}
public class TestThread implements Runnable {
private final WaitNotifyDemo demo;
public TestThread(WaitNotifyDemo demo) { this.demo = demo; }
public void run() { demo.doSomething(); }
public static void main(String[] args) {
WaitNotifyDemo demo = new WaitNotifyDemo(4, "DAVID");
new Thread(new TestThread(demo)).start();
new Thread(new TestThread(demo)).start();
new Thread(new TestThread(demo)).start();
new Thread(new TestThread(demo)).start();
}
}
Thread-0-<DAVID3> will invoke WAIT()
Thread-1-<DAVID2> will invoke WAIT()
Thread-2-<DAVID1> will invoke WAIT()
Thread-3-<DAVID0>::INVOKED NOTIFYALL() AND FINISHED
Thread-0-<DAVID2> has been ACTIVED
Thread-0-<DAVID2> will invoke WAIT()
Thread-1-<DAVID1> has been ACTIVED
Thread-1-<DAVID1> will invoke WAIT()
Thread-2-<DAVID0> has been ACTIVED
Thread-2-<DAVID0>::INVOKED NOTIFYALL() AND FINISHED
Thread-0-<DAVID1> has been ACTIVED
Thread-0-<DAVID1> will invoke WAIT()
Thread-1-<DAVID0> has been ACTIVED
Thread-1-<DAVID0>::INVOKED NOTIFYALL() AND FINISHED
Thread-0-<DAVID0> has been ACTIVED
Thread-0-<DAVID0>::INVOKED NOTIFYALL() AND FINISHED
所有線程依次喚醒、遞減計數、最終全部結束。
Thread-1-<DAVID3> will invoke WAIT()
Thread-0-<DAVID2> will invoke WAIT()
Thread-2-<DAVID1> will invoke WAIT()
Thread-3-<DAVID0>::INVOKED NOTIFY() AND FINISHED
Thread-1-<DAVID2> has been ACTIVED
Thread-1-<DAVID2> will invoke WAIT()
...(後續無輸出,程序掛起)
分析:notify() 隨機喚醒一個等待線程,大概率喚醒的是仍需等待的線程(而非已滿足條件的),導致已滿足條件的線程永久留在等待集,系統停滯。
while (!condition) wait(); 防虛假喚醒。notifyAll(),確保被喚醒線程見到最新狀態。CountDownLatch、CyclicBarrier、Semaphore、BlockingQueue、Lock/Condition 等,語義更清晰、不易出錯。Lock 支持多條件變量(newCondition()),精準喚醒特定條件等待線程,避免驚群效應。wait/notify/notifyAll 是基於對象監視器的底層協作機制,notify() 隨機單喚醒極易導致信號丟失與線程飢餓,生產代碼中強烈建議使用 notifyAll() 或更高級的併發組件。理解等待集、同步隊列、虛假喚醒、鎖釋放時機,是編寫正確並發程序的前提。