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

Java 信號量模型實戰手冊:wait/notify 核心機制與生產者消費者模式

     編輯:Java 相關知識

從同步鎖到信號量:同步模式演進

同步模型適用場景核心機制限制
同步鎖互斥存取共享資源synchronized / Lock單一線程執行,無條件協作
信號量條件協作、生產消費、讀寫分離wait/notify / Condition需配合鎖使用,邏輯較複雜

Java 無專門 Semaphore/Mutex 物件(JDK 5+ 提供 java.util.concurrent.Semaphore),但任意物件皆可作為鎖與信號量:

// 同一物件既是鎖,也是信號量
Object signal = new Object();

// 等待通知
synchronized (signal) {
    signal.wait(); // 釋放鎖,進入等待隊列
}

// 發送通知
synchronized (signal) {
    signal.notify(); // 喚醒一個等待線程
}

核心 API 深度解析

Object.wait() 三種形式

signal.wait();           // 無限等待,直到被 notify
signal.wait(5000);       // 最多等待 5 秒,超時自動喚醒
signal.wait(5000, 500000); // 納秒級精度(實際取決於 OS)

關鍵語意:

  1. 當前線程必須持有該物件鎖(在 synchronized 內)
  2. 釋放鎖並進入該物件的 等待集合
  3. 被喚醒後,重新競爭鎖,獲得後才繼續執行
  4. 可能發生虛假喚醒,必須用迴圈檢查條件

正確等待模式(標準範式)

synchronized (lock) {
    while (!condition) { // 迴圈而非 if!
        lock.wait();
    }
    // 條件滿足,執行業務邏輯
}

notify() vs notifyAll()

方法行為適用場景風險
notify()隨機喚醒一個等待線程所有等待者處理邏輯相同(如連線池)喚醒錯誤線程導致「信號丟失」
notifyAll()喚醒所有等待線程等待條件不同、或為保險起見驚群效應,效能較差

經典實戰:生產者-消費者模式

共享緩衝區

public class BoundedBuffer {
    private final List buffer = new ArrayList<>();
    private final int capacity;
    
    public BoundedBuffer(int capacity) {
        this.capacity = capacity;
    }
    
    // 生產者呼叫
    public synchronized void put(T item) throws InterruptedException {
        while (buffer.size() == capacity) {
            wait(); // 緩衝區滿,等待消費者
        }
        buffer.add(item);
        notifyAll(); // 喚醒消費者
    }
    
    // 消費者呼叫
    public synchronized T take() throws InterruptedException {
        while (buffer.isEmpty()) {
            wait(); // 緩衝區空,等待生產者
        }
        T item = buffer.remove(0);
        notifyAll(); // 喚醒生產者
        return item;
    }
}

執行緒啟動

BoundedBuffer buffer = new BoundedBuffer<>(5);

// 生產者
new Thread(() -> {
    for (int i = 0; i < 100; i++) {
        buffer.put(i);
        System.out.println("生產: " + i);
        sleep(100);
    }
}, "Producer").start();

// 消費者
new Thread(() -> {
    for (int i = 0; i < 100; i++) {
        Integer item = buffer.take();
        System.out.println("消費: " + item);
        sleep(150);
    }
}, "Consumer").start();

進階替代方案:JDK 5+ 並發包

1. BlockingQueue —— 現代首選

BlockingQueue queue = new ArrayBlockingQueue<>(5);

// 生產者
queue.put(item); // 滿時自動阻塞

// 消費者
Integer item = queue.take(); // 空時自動阻塞

優點:無需手寫 wait/notify、支援超時、可中斷、公平策略、內建生產消費語意。

2. Condition —— 更靈活的信號量

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

lock.lock();
try {
    while (buffer.size() == capacity) notFull.await();
    buffer.add(item);
    notEmpty.signal();
} finally {
    lock.unlock();
}

優點:一個鎖支援多條件變數、可中斷等待、公平/非公平可選。

3. Semaphore —— 計數信號量

Semaphore permits = new Semaphore(5); // 5 個許可證

// 獲取許可
permits.acquire();
// 執行受限操作
permits.release();

適用:連線池、限流、資源存取控制。

常見錯誤與避坑指南

錯誤寫法問題正確寫法
if (empty) wait();虛假喚醒導致條件不成立仍執行while (empty) wait();
非 synchronized 內呼叫 wait()IllegalMonitorStateException必須在 synchronized(lock) 內
notify() 後立即釋放鎖被喚醒線程無法立即獲得鎖在 synchronized 區塊結束前呼叫 notify()
wait() 未處理 InterruptedException中斷狀態丟失,線程無法優雅停止拋出或重設中斷旗標

總結

Java 信號量模型核心是 wait/notify 機制,本質是「鎖 + 條件隊列」。雖語法簡單,但正確使用需遵守嚴格規範(迴圈檢查、同步塊、異常處理)。現代開發強烈建議直接使用 BlockingQueue、Condition、Semaphore 等高層抽象,它們封裝了經驗證的最佳實踐,減少併發 Bug 風險,提升程式碼可讀性與維護性。

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