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

Java 線程同步核心:wait/notify/notifyAll 機制詳解與實戰範式

     編輯:Java 相關知識

核心前提:同步前提下的協作

wait()、notify()、notifyAll() 是 Object 的 final 方法,**必須在同步上下文中調用** (同步方法或 `synchronized` 代碼塊),否則拋出 IllegalMonitorStateException。它們操作的是當前同步鎖對象的等待集。

三大方法語義精講

方法行為鎖狀態線程狀態變遷喚醒後動作
wait()釋放鎖,進入等待集,無限期阻塞釋放RUNNABLE → WAITING進入鎖池競爭鎖,獲取後從 wait() 返回
wait(long timeout)釋放鎖,超時自動喚醒釋放RUNNABLE → TIMED_WAITING同上,或超時後進入鎖池
notify()隨機喚醒一個等待線程持有 (調用者仍持有鎖)WAITING → BLOCKED (進入鎖池)喚醒線程在鎖池排隊
notifyAll()喚醒所有等待線程持有WAITING → BLOCKED所有喚醒線程在鎖池競爭

經典範式:生產者-消費者模型

public class WaitNotifyDemo {
    // 共享資源:容量為 1 的緩衝區
    private static final Object lock = new Object();
    private static Integer buffer = null; // null 表示空

    public static void main(String[] args) {
        new Thread(WaitNotifyDemo::producer, "Producer").start();
        new Thread(WaitNotifyDemo::consumer, "Consumer").start();
    }

    // 生產者
    static void producer() {
        for (int i = 1; i <= 10; i++) {
            synchronized (lock) {
                // 1. 緩衝區滿,等待
                while (buffer != null) {
                    try {
                        System.out.println(Thread.currentThread().getName() + " 緩衝區滿,等待...");
                        lock.wait(); // 釋放鎖,進入等待集
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                        return;
                    }
                }
                // 2. 生產數據
                buffer = i;
                System.out.println(Thread.currentThread().getName() + " 生產: " + buffer);
                // 3. 唤醒消費者
                lock.notifyAll(); // 推薦 notifyAll 避免信號丟失
            }
            // 模擬生產耗時
            sleepRandom(100);
        }
    }

    // 消費者
    static void consumer() {
        for (int i = 1; i <= 10; i++) {
            synchronized (lock) {
                // 1. 緩衝區空,等待
                while (buffer == null) {
                    try {
                        System.out.println(Thread.currentThread().getName() + " 緩衝區空,等待...");
                        lock.wait();
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                        return;
                    }
                }
                // 2. 消費數據
                System.out.println(Thread.currentThread().getName() + " 消費: " + buffer);
                buffer = null;
                // 3. 唤醒生產者
                lock.notifyAll();
            }
            sleepRandom(150);
        }
    }

    static void sleepRandom(int max) {
        try { Thread.sleep((long) (Math.random() * max)); } catch (InterruptedException ignored) {}
    }
}

關鍵細節:為什麼用 while 而非 if?

// ❌ 錯誤:if 檢查,虛假喚醒/多線程競爭下會出錯
if (buffer != null) lock.wait();

// ✅ 正確:while 循環檢查,防禦性編程
while (buffer != null) lock.wait();

三大風險:

  1. 虛假喚醒:線程在無 notify 情況下意外醒來 (JVM 允許)。
  2. 搶鎖失敗:被喚醒後進入鎖池,搶不到鎖繼續阻塞,此時條件可能已被其他線程改變。
  3. 多消費者/生產者:notify 隨機喚醒一個,可能喚醒同類角色 (如喚醒另一個生產者),導致信號丟失。結論:生產環境強制使用 while + notifyAll。

wait vs sleep 核心區別

維度wait()sleep()
所屬類ObjectThread
鎖行為釋放鎖不釋放鎖
喚醒方式notify/notifyAll / 中斷 / 超時超時 / 中斷
調用前提必須持有鎖 (同步塊中)無前提,任意位置
典型用途線程協作、條件等待定時延遲、節流

實戰避坑清單

  1. 鎖對象一致性:wait/notify 必須作用於**同一個鎖對象** (示例中均為 `lock`)。
  2. 異常處理:`InterruptedException` 必須恢復中斷狀態 (`Thread.currentThread().interrupt()`) 或拋出,不可吞噬。
  3. notifyAll 優先:除非極度確定單生產單消費且性能極其敏感,默認用 `notifyAll`。
  4. 條件狀態檢查:共享狀態變量 (如 `buffer`) 必須在同步塊內讀寫,保證可見性。
  5. 不要在鎖上調用非同步方法:同步塊內調用耗時 IO/睡眠會長期持有鎖,殺死並發性能。

現代替代方案:Condition & Lock

JDK 1.5+ `java.util.concurrent.locks` 提供更強大、更靈活的協作機制:

Lock lock = new ReentrantLock();
Condition notFull = lock.newCondition();
Condition notEmpty = lock.newCondition();

// 生產者
lock.lock();
try {
    while (buffer != null) notFull.await(); // 等待不滿
    buffer = value;
    notEmpty.signal(); // 喚醒消費者
} finally { lock.unlock(); }

優勢:一把鎖可關聯多個 Condition (精準喚醒)、支持公平鎖、可中斷鎖獲取、超時獲取、條件隊列可檢查。

總結

wait/notify 是 Java 線程協作的**原語級基礎**,理解其「釋放鎖→等待→喚醒→搶鎖→繼續」的完整生命周期,配合 **while 循環檢查 + notifyAll** 範式,是寫正確併發代碼的必修內功。現代開發推薦使用 `Lock + Condition` 或更高級併發組件 (`BlockingQueue`, `CompletableFuture`, `StampedLock`),減少底層同步錯誤風險。

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