在多線程開發中,線程間往往需要協調工作。典型場景:瀏覽器中,顯示線程 需等待 下載線程 完成圖片下載才能渲染。若圖片未就緒,顯示線程應暫停(等待);下載完成後,下載線程通知顯示線程「可以顯示了」,顯示線程恢復執行。
這邏輯用 pseudocode 表示:
// 等待方
if (條件不滿足) {
等待();
}
// 繼續執行
// 通知方
條件 = true;
通知等待線程();
Java 中這一機制的核心是 Object.wait() 與 Object.notify()/notifyAll(),且必須配合鎖(synchronized)使用。
synchronized(obj) {
while (!condition) { // 必須用 while 防止虛假喚醒
obj.wait();
}
obj.doSomething(); // 條件滿足,執行業務邏輯
}
執行流程:
obj 鎖,檢查條件不滿足,調用 obj.wait() 釋放鎖並進入等待隊列。obj 鎖,修改條件使其滿足,調用 obj.notify() 喚醒一個等待線程(或 notifyAll() 喚醒全部)。wait() 返回繼續執行。wait()、notify()、notifyAll() 均為 Object 實例方法,調用前必須持有該對象的鎖,否則拋出 IllegalMonitorStateException。
wait():線程釋放鎖,進入 WAITING 狀態。notify():線程不釋放鎖,只是將目標線程從等待隊列移至同步隊列(BLOCKED 狀態),等待鎖釋放。synchronized 塊,鎖才真正釋放,被喚醒線程才有機會獲鎖繼續。wait() 返回時,線程已重新獲得鎖。這意味著喚醒與繼續執行之間存在時間差,條件可能再次發生變化,因此必須用 while 循環檢查條件,不能用 if。
notify():JVM 隨機選擇一個等待線程喚醒(適合「單生產者單消費者」或等待條件相同的場景)。notifyAll():喚醒所有等待線程,它們競爭鎖,獲得鎖的繼續執行,其餘重新進入 BLOCKED 狀態(適合條件不同、需重新檢查的場景)。被喚醒線程雖從 wait() 返回,但無法立即獲鎖,必須等通知者退出 synchronized 塊。這保證了條件修改的原子可見性。
public class SharedBuffer {
private final List buffer = new ArrayList<>();
private final int capacity = 10;
public synchronized void produce(int value) throws InterruptedException {
while (buffer.size() == capacity) {
wait(); // 緩衝區滿,等待消費
}
buffer.add(value);
System.out.println("Produced: " + value + ", Size: " + buffer.size());
notifyAll(); // 通知消費者
}
public synchronized int consume() throws InterruptedException {
while (buffer.isEmpty()) {
wait(); // 緩衝區空,等待生產
}
int value = buffer.remove(0);
System.out.println("Consumed: " + value + ", Size: " + buffer.size());
notifyAll(); // 通知生產者
return value;
}
}
Condition, BlockingQueue),避免手動管理 wait/notify 的易錯細節。掌握 wait/notify 協作機制,是編寫正確、高效多線程程序的基石。實戰中建議優先使用高層並發工具,但在理解底層原理、排查死鎖/活鎖時,這些知識不可或缺。