在 Java 多線程同步中,鎖池與等待池是兩個至關重要卻常被混淆的概念。它們共同構成了 synchronized、wait()、notify() 協作的運作基礎。
以下代碼演示一個共享計數器,生產者遞增,消費者遞減,通過 wait/notify 實現精準協作:
public class Target {
private int count = 0;
// 生產者調用
public synchronized void increase() {
// 1. 條件不滿足,進入等待池
if (count == 2) {
try {
wait(); // 釋放鎖,進入等待池
} catch (InterruptedException e) {
e.printStackTrace();
}
}
count++;
System.out.println(Thread.currentThread().getName() + ": " + count);
// 2. 唤醒等待池中的消費者
notify();
}
// 消費者調用
public synchronized void decrease() {
if (count == 0) {
try {
wait(); // 釋放鎖,進入等待池
} catch (InterruptedException e) {
e.printStackTrace();
}
}
count--;
System.out.println(Thread.currentThread().getName() + ": " + count);
// 唤醒生產者
notify();
}
}
| 維度 | 鎖池 | 等待池 |
|---|---|---|
| 觸發條件 | 線程嘗試獲取 synchronized 鎖失敗 | 線程調用 wait() 主動釋放鎖 |
| 線程狀態 | BLOCKED (阻塞) | WAITING / TIMED_WAITING (等待) |
| 持有鎖 | 不持有鎖,排隊等待獲取 | 已釋放鎖,不佔用鎖資源 |
| 喚醒方式 | 持有鎖的線程釋放鎖 (退出同步塊) | 其他線程調用 notify()/notifyAll() |
| 喚醒後去向 | 直接嘗試獲取鎖,成功則繼續執行 | 進入鎖池,重新競爭鎖 |
wait() → 釋放鎖 → 進入等待池。notify()。wait() 後繼續執行。否則拋出 IllegalMonitorStateException。因為只有持有鎖的線程才有資格釋放鎖並等待。
// 錯誤示範:sleep 不釋放鎖,其他線程無法進入
synchronized (obj) {
Thread.sleep(1000); // 鎖依然被佔用!
}
// 正確:wait 釋放鎖,讓出 CPU 並允許其他線程進入
synchronized (obj) {
obj.wait(1000); // 鎖被釋放,1秒後自動喚醒進入鎖池
}
notify():隨機喚醒一個等待線程,可能產生「信號丟失」問題(喚醒的線程不是預期的那個)。notifyAll():喚醒所有等待線程,雖然會增加鎖競爭,但更安全,推薦在條件變量模式中使用。// 標準範式:防止虛假喚醒
synchronized (lock) {
while (conditionNotMet) {
lock.wait();
}
// 執行業務邏輯
}
鎖池是「想進去卻進不去」的隊列,等待池是「主動出去等通知」的休息區。wait() 是橋樑,notify() 是傳令兵。理解這兩個池的流轉關係,是掌握 Java 線程協作的核心鑰匙。