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

Java 多線程通信機制詳解:共享變量、wait/notify、條件變量與信號丟失防範

     編輯:Java 綜合教學

Java 多線程通信機制詳解:共享變量、wait/notify、條件變量與信號丟失防範

線程通信核心是「等待-通知」模式:線程等待條件滿足,另一線程滿足條件後通知。Java 提供 Object.wait/notify/notifyAll 及 java.util.concurrent.locks.Condition 兩套機制。本文從共享變量模型進階到條件變量,分析信號丟失、虛假喚醒、鎖釋放等關鍵細節。

一、共享變量輪詢(Busy Wait)

class MySignal {
    boolean hasDataToProcess = false;
    synchronized boolean hasData() { return hasDataToProcess; }
    synchronized void setHasData(boolean b) { hasDataToProcess = b; }
}
// 消費者線程
while (!sharedSignal.hasData()) { /* busy wait */ }

缺點:CPU 空轉,無法進入休眠,僅適合極短等待場景。

二、Object.wait/notify 機制

基本用法

class MyWaitNotify {
    final Object monitor = new Object();
    boolean wasSignalled = false;

    void doWait() throws InterruptedException {
        synchronized (monitor) {
            while (!wasSignalled) monitor.wait();
            wasSignalled = false; // 清除信號
        }
    }
    void doNotify() {
        synchronized (monitor) {
            wasSignalled = true;
            monitor.notify(); // 或 notifyAll()
        }
    }
}

關鍵規則

  • 調用 wait/notify 必須持有監視器鎖(在 synchronized 塊內)
  • wait() 釋放鎖並進入等待集,被喚醒後重新競爭鎖
  • notify() 隨機喚醒一個等待線程,notifyAll() 喚醒全部

三、信號丟失與防範

場景:通知線程在等待線程調用 wait() 前調用 notify(),信號丟失,等待線程永久阻塞。
解決:引入布爾標誌 wasSignalled 保存信號狀態:

  • 通知前設置 wasSignalled = true
  • 等待前檢查 if (!wasSignalled) wait()
  • 被喚醒後清除 wasSignalled = false

四、虛假喚醒與 while 循環

線程可能在無 notify 情況下喚醒(JVM 實現細節)。
對策:必須在 while 循環中調用 wait():

while (!wasSignalled) {
    monitor.wait();
}

喚醒後重新檢查條件,不滿足則繼續等待。if 判斷無法應對虛假喚醒。

五、多線程等待同一信號

使用 notifyAll() + while 循環:

  • 所有等待線程被喚醒
  • 只有首個獲得鎖的線程看到 wasSignalled=true,執行並清除標誌
  • 其餘線程重新檢查標誌為 false,繼續等待

若用 notify() 可能喚醒非目標線程導致信號實質丟失。

六、監視器對象選擇陷阱

錯誤:使用字符串常量 "" 或全局對象作為鎖:

synchronized ("") { wait(); } // 危險!

原因:字符串常量池導致不同類共享同一鎖對象,引發誤喚醒、信號丟失。
正確:每個協作類使用私有 final Object monitor = new Object()。

共享字符串鎖風險示意

七、Condition 接口(現代替代)

ReentrantLock 配合 Condition 提供更強大的等待-通知:

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

void take() throws InterruptedException {
    lock.lock();
    try {
        while (queue.isEmpty()) notEmpty.await();
        return queue.poll();
    } finally { lock.unlock(); }
}
void put(E e) {
    lock.lock();
    try {
        queue.add(e);
        notEmpty.signal(); // 或 signalAll()
    } finally { lock.unlock(); }
}

優勢:一把鎖可創建多個 Condition(如 notEmpty/notFull),支持公平鎖、可中斷等待、超時等待。

總結

  • 共享變量 + wait/notify 是基礎,Condition 是進階選擇
  • 必須在 synchronized/Lock 保護下檢查條件並等待
  • while 循環 + 狀態標誌 雙重防禦信號丟失與虛假喚醒
  • 私有監視器對象,拒絕字符串常量/全局對象
  • 生產代碼優先使用 BlockingQueue、CountDownLatch、CompletableFuture 等高級組件
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved