許多開發者認為:notify() 隨機喚醒一個線程,notifyAll() 喚醒所有線程並「同時繼續運行」。這是錯誤的。 關鍵在於:被喚醒線程必須重新獲得鎖才能繼續執行,鎖同一時刻只能被一個線程持有。
wait()導致當前線程放入對象等待集,釋放所有同步鎖權利。線程禁用直到:① 被notify()選中、② 被notifyAll()喚醒、③ 中斷、④ 超時。隨後線程從等待集移除,**重新與其他線程競爭鎖**;獲得鎖後恢復同步狀態,從wait()返回。
private static final Object LOCK = new Object();
static class Worker implements Runnable {
private final int id;
Worker(int id) { this.id = id; }
@Override public void run() {
synchronized (LOCK) {
System.out.println("Thread-" + id + " 進入等待");
try { LOCK.wait(); } catch (InterruptedException ignored) {}
System.out.println("Thread-" + id + " 獲得鎖,開始執行");
try { Thread.sleep(30_000); } catch (InterruptedException ignored) {} // 持有鎖 30s
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread[] threads = new Thread[10];
for (int i = 0; i < 10; i++) threads[i] = new Thread(new Worker(i));
for (Thread t : threads) t.start();
Thread.sleep(5_000); // 確保全部進入 wait
synchronized (LOCK) { LOCK.notifyAll(); } // 喚醒全部
}
Thread-0 進入等待
...
Thread-9 進入等待
Thread-9 獲得鎖,開始執行
... (30秒內無其他輸出) ...
結論:雖然 10 個線程全被喚醒,但只有搶到鎖的 Thread-9 能執行;其餘 9 個線程在 synchronized(LOCK) 入口阻塞,直到 Thread-9 釋放鎖後再次競爭。
| 維度 | notify() | notifyAll() |
|---|---|---|
| 喚醒數量 | 隨機選一個 | 全部 |
| 後續競爭 | 被喚醒者與其他嘗試獲取鎖的線程競爭 | 所有被喚醒者 + 其他線程共同競爭 |
| 適用場景 | 等待集中線程執行同類任務(如線程池工作者),喚醒一個足矣 | 等待條件不同、需精確通知特定條件變更、防止信號丟失 |
| 風險 | 若被喚醒者發現條件仍不滿足且不再 wait(),會導致「信號丟失」,其他滿足條件的線程永久等待 | 驚群效應:大量線程同時競爭鎖,上下文切換開銷大 |
// 僅一個生產者、一個消費者、緩衝區大小1
synchronized (lock) {
while (buffer.full()) lock.wait();
buffer.put(item);
lock.notify(); // 只有一個消費者在等,喚醒它即可
}
// 多生產者、多消費者、緩衝區任意大小
synchronized (lock) {
while (buffer.full()) lock.wait();
buffer.put(item);
lock.notifyAll(); // 可能有多個消費者在等,或生產者也在等不滿條件
}
Lock 配合多個 Condition 可實現精確通知:
ReentrantLock lock = new ReentrantLock();
Condition notFull = lock.newCondition();
Condition notEmpty = lock.newCondition();
// 生產者
lock.lock();
try {
while (buffer.full()) notFull.await();
buffer.put(item);
notEmpty.signal(); // 精確喚醒消費者
} finally { lock.unlock(); }
// 消費者
lock.lock();
try {
while (buffer.empty()) notEmpty.await();
Item item = buffer.take();
notFull.signal(); // 精確喚醒生產者
} finally { lock.unlock(); }
優勢:避免驚群、語義清晰、支持多條件變量。
notifyAll 更安全。Thread.join() 依賴此鎖,業務代碼干擾會導致不可預期行為。notify 與 notifyAll 的核心差別不在「喚醒數量」,而在「後續鎖競爭範圍」。被喚醒只是入場券,獲得鎖才是演出開始。現代併發編程推薦使用 Lock + Condition 替代內置監視器,實現更精確、高效、可維護的線程協作。