首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java notify vs notifyAll:深度解析喚醒機制與鎖競爭本質

     編輯:Java 相關知識

常見誤區

許多開發者認為:notify() 隨機喚醒一個線程,notifyAll() 喚醒所有線程並「同時繼續運行」。這是錯誤的。 關鍵在於:被喚醒線程必須重新獲得鎖才能繼續執行,鎖同一時刻只能被一個線程持有。

JDK 文檔核心定義

wait() 導致當前線程放入對象等待集,釋放所有同步鎖權利。線程禁用直到:① 被 notify() 選中、② 被 notifyAll() 喚醒、③ 中斷、④ 超時。隨後線程從等待集移除,**重新與其他線程競爭鎖**;獲得鎖後恢復同步狀態,從 wait() 返回。

實證代碼:notifyAll 也只能串行執行

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 的本質區別

維度notify()notifyAll()
喚醒數量隨機選一個全部
後續競爭被喚醒者與其他嘗試獲取鎖的線程競爭所有被喚醒者 + 其他線程共同競爭
適用場景等待集中線程執行同類任務(如線程池工作者),喚醒一個足矣等待條件不同、需精確通知特定條件變更、防止信號丟失
風險若被喚醒者發現條件仍不滿足且不再 wait(),會導致「信號丟失」,其他滿足條件的線程永久等待驚群效應:大量線程同時競爭鎖,上下文切換開銷大

經典生產者-消費者模式對比

使用 notify(需嚴格條件)

// 僅一個生產者、一個消費者、緩衝區大小1
synchronized (lock) {
    while (buffer.full()) lock.wait();
    buffer.put(item);
    lock.notify(); // 只有一個消費者在等,喚醒它即可
}

使用 notifyAll(通用安全)

// 多生產者、多消費者、緩衝區任意大小
synchronized (lock) {
    while (buffer.full()) lock.wait();
    buffer.put(item);
    lock.notifyAll(); // 可能有多個消費者在等,或生產者也在等不滿條件
}

進階:Condition 替代方案(JDK 1.5+)

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(); }

優勢:避免驚群、語義清晰、支持多條件變量。

選型指南

  1. 默認用 notifyAll:除非極其確定等待集線程同質且條件單一,否則 notifyAll 更安全。
  2. 高併發優先用 Condition:精確通知、減少無效競爭、代碼可讀性強。
  3. 避免在 Thread 實例上 wait/notify:JVM 內部使用 Thread.join() 依賴此鎖,業務代碼干擾會導致不可預期行為。
  4. 始終在 while 循環中 wait:防止虛假喚醒(spurious wakeup)。

總結

notify 與 notifyAll 的核心差別不在「喚醒數量」,而在「後續鎖競爭範圍」。被喚醒只是入場券,獲得鎖才是演出開始。現代併發編程推薦使用 Lock + Condition 替代內置監視器,實現更精確、高效、可維護的線程協作。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved