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

Java 線程通信核心機制:wait/notify 同步模式源碼級解析

     編輯:Java 相關知識

核心問題:為什麼 wait/notify 必須在同步塊中?

Java 線程通信依賴對象監視器。wait() 釋放鎖並進入等待集,notify() 喚醒等待集中的線程。兩者必須持有同一對象的鎖,否則拋出 IllegalMonitorStateException。

錯誤代碼剖析:原文示例的致命缺陷


// 原文代碼片段 - 錯誤示範
class ThreadNum extends Thread {
    Object o; // 共享對象
    public void run() {
        synchronized (this) { // 鎖住 ThreadNum 實例
            this.wait();      // 在 this 上等待
        }
        synchronized (this) { // 鎖住 ThreadNum 實例
            this.notify();    // 在 this 上通知
        }
    }
}

class ThreadChar extends Thread {
    Object o; // 同一共享對象引用
    public void run() {
        synchronized (this) { // 鎖住 ThreadChar 實例(不同鎖!)
            this.notify();
        }
        synchronized (this) { // 鎖住 ThreadChar 實例
            this.wait();
        }
    }
}

錯誤本質

  • 兩線程分別同步於 各自的 Thread 實例(this 指向不同對象)
  • 共享對象 o 從未被用作鎖
  • wait()/notify() 作用於不同監視器,永無法喚醒對方
  • 運行結果:死鎖或隨機輸出,無法實現交替打印

正確實現:共享對象作為同步鎖


public class WaitNotifyDemo {
    static final Object lock = new Object(); // 共享鎖對象
    static volatile boolean numberTurn = true; // 協調標誌
    
    public static void main(String[] args) {
        new Thread(() -> printNumbers(lock)).start();
        new Thread(() -> printChars(lock)).start();
    }
    
    static void printNumbers(Object lock) {
        synchronized (lock) {
            for (int i = 1; i <= 26; i++) {
                while (!numberTurn) {
                    try { lock.wait(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); return; }
                }
                System.out.print(i + " ");
                numberTurn = false;
                lock.notify();
            }
        }
    }
    
    static void printChars(Object lock) {
        synchronized (lock) {
            for (char c = 'A'; c <= 'Z'; c++) {
                while (numberTurn) {
                    try { lock.wait(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); return; }
                }
                System.out.print(c + " ");
                numberTurn = true;
                lock.notify();
            }
        }
    }
}

關鍵修正點

  1. 單一共享鎖:lock 對象被兩線程同步
  2. 條件變量模式:用 while (condition) wait() 防止虛假喚醒
  3. 狀態變量 volatile:保證線程間可見性
  4. 通知在狀態更新後:先改狀態,再 notify()

JVM 監視器機制深度解析

每個 Java 對象邏輯上關聯一個監視器:

  • 進入區:線程嘗試獲取鎖
  • 擁有者:持有鎖的線程
  • 等待集:調用 wait() 釋放鎖並進入的線程隊列

wait() 語義:釋放鎖 → 進入等待集 → 休眠。
notify() 語義:從等待集移出一線程 → 進入進入區競爭鎖 → 獲取鎖後從 wait() 返回。

進階替代方案:java.util.concurrent 組件

組件適用場景優勢
BlockingQueue生產者-消費者內置阻塞/喚醒,無需手動 wait/notify
CountDownLatch一等多/多等一計數器語義清晰,不可重置
CyclicBarrier多線程匯聚點可重置,支持屏障動作
Phaser動態階段同步靈活的階段註冊/註銷
CompletableFuture異步流水線函數式組合,異常傳播
Lock/Condition精細控制多條件變量、可中斷、公平鎖

最佳實踐總結

  1. 優先使用高級併發工具:wait/notify 易錯、難維護,屬於底層基礎設施
  2. 若必須使用:共享鎖對象、while 循環檢查條件、volatile 狀態變量
  3. 避免在 Thread 子類中同步 this:Thread 實例不應作為業務鎖
  4. 文檔化協議:明確哪個對象是鎖、條件變量含義、喚醒時機

結語

Java 線程通信的本質是共享鎖對象上的條件等待與通知。原文示例犯下的「各自鎖各自」錯誤是典型反面教材。現代開發應拥抱 java.util.concurrent 提供的成熟抽象,將同步協調委託給經過驗證的組件,專注於業務邏輯。理解監視器機制、虛假喚醒、內存可見性等底層原理,是正確使用高級工具、排查疑難併發問題的基石。

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