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

Java 並發編程:Condition 條件變量深度解析與生產者消費者模式實戰

     編輯:Java 相關知識

Java 並發核心組件:Condition 條件變量完整指南

java.util.concurrent.locks.Condition 是 JDK 1.5 引入的進階線程協調機制,提供比 Object.wait/notify 更精確、更靈活的線程等待/通知能力。本文系統對比兩種機制,詳細講解 Condition API、基本用法、多條件變量精確控制的經典生產者-消費者模式實現。

Condition vs Object 監視器方法對比

功能Object 監視器Condition
等待wait()await()
超時等待wait(timeout)await(time, unit) / awaitNanos / awaitUntil
不可中斷等待無awaitUninterruptibly()
單線程喚醒notify()signal()
全線程喚醒notifyAll()signalAll()
綁定鎖synchronized (內置鎖)Lock 實現(ReentrantLock 等)
多條件支持無(單一等待隊列)支持多 Condition 實例

Condition 核心 API

void await()                    // 等待直到被喚醒/中斷
boolean await(long time, TimeUnit unit)  // 超時等待,返回是否超時
long awaitNanos(long nanosTimeout)       // 納秒級超時,返回剩餘時間
void awaitUninterruptibly()              // 忽略中斷等待
boolean awaitUntil(Date deadline)        // 截止時間等待
void signal()                     // 喚醒一個等待線程
void signalAll()                  // 喚醒所有等待線程

基礎用法:替換 Object.wait/notify

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

// 等待線程
lock.lock();
try {
    condition.await();  // 釋放鎖並等待
} finally {
    lock.unlock();
}

// 喚醒線程
lock.lock();
try {
    condition.signal();  // 喚醒一個等待線程
} finally {
    lock.unlock();
}

關鍵差異:Condition 必須在 持有鎖狀態下 調用 await/signal,否則拋出 IllegalMonitorStateException。

進階場景:有界緩衝區——多條件變量精確控制

經典問題:單一 notifyAll() 無法區分喚醒「生產者」還是「消費者」,導致虛假喚醒、效率低下。

BoundedBuffer 實現

class BoundedBuffer {
    final Lock lock = new ReentrantLock();
    final Condition notFull = lock.newCondition();   // 控制生產者
    final Condition notEmpty = lock.newCondition();  // 控制消費者
    
    final Object[] items = new Object[5];
    int putptr, takeptr, count;

    public void put(Object x) throws InterruptedException {
        lock.lock();
        try {
            while (count == items.length) notFull.await();  // 滿則等待
            items[putptr] = x;
            if (++putptr == items.length) putptr = 0;
            ++count;
            notEmpty.signal();  // 喚醒消費者
            System.out.println(Thread.currentThread().getName() + " put " + x);
        } finally { lock.unlock(); }
    }

    public Object take() throws InterruptedException {
        lock.lock();
        try {
            while (count == 0) notEmpty.await();  // 空則等待
            Object x = items[takeptr];
            if (++takeptr == items.length) takeptr = 0;
            --count;
            notFull.signal();  // 喚醒生產者
            System.out.println(Thread.currentThread().getName() + " take " + x);
            return x;
        } finally { lock.unlock(); }
    }
}

測試驗證

BoundedBuffer bb = new BoundedBuffer();
for (int i = 0; i < 10; i++) {
    new Thread(() -> { bb.put(i); }, "P" + i).start();
    new Thread(() -> { bb.take(); }, "C" + i).start();
}

執行流程分析(容量 5)

  1. P1~P5 連續寫入 1,4,5,0,2 → 緩衝區滿
  2. P6~P9 調用 notFull.await() 阻塞
  3. C0 取出 1 → 調用 notFull.signal() 喚醒 P6
  4. P6 寫入 3 → 調用 notEmpty.signal() 喚醒 C1
  5. 交替進行,精確喚醒對應角色線程

核心設計模式

  1. 狀態檢查用 while 而非 if:防止虛假喚醒(spurious wakeup)
  2. await() 自動釋放鎖並阻塞,被喚醒後重新獲取鎖
  3. signal() 不釋放鎖,喚醒線程進入同步隊列競爭鎖
  4. 多 Condition 實例綁定同一 Lock:實現等待隊列分離

最佳實踐建議

  • 優先使用 ReentrantLock + Condition 替代 synchronized + wait/notify
  • 每個業務邏輯條件創建獨立 Condition(如 notFull/notEmpty、hasTask/hasSpace)
  • 超時等待使用 await(time, unit) 返回 boolean 判斷是否超時
  • 避免在持有鎖期間執行耗時操作(I/O、睡眠)

總結

Condition 通過顯式鎖綁定、多等待隊列分離、豐富等待/喚醒語義三大特性,解決了內置鎖機制表達能力不足的問題。掌握 Condition 精確控制線程協調,是構建高性能併發組件(線程池、阻塞隊列、信號量、柵欄)的核心技能。

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