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

Java 多線程基礎:生產者-消費者模式的 wait/notify 實現

     編輯:Java 相關知識

前言

生產者-消費者是多線程協作的經典模型。本文通過「倉庫-產品-生產者-消費者」領域模型,展示如何用 synchronized + wait()/notifyAll() 實現線程安全的緩衝區協作,並分析關鍵細節與常見陷阱。

一、領域模型設計

角色職責關鍵方法
Depot(倉庫)共享緩衝區,維護容量 capacity 與當前庫存 sizeproduce(val)、consume(val)
Producer(生產者)持有 Depot 引用,新建線程調用 produce()produce(val)
Customer(消費者)持有 Depot 引用,新建線程調用 consume()consume(val)

二、核心實現:Depot 類

class Depot {
    private int capacity; // 倉庫容量
    private int size;     // 當前庫存

    public Depot(int capacity) { this.capacity = capacity; this.size = 0; }

    public synchronized void produce(int val) {
        try {
            int left = val; // 剩餘待生產量
            while (left > 0) {
                // 倉滿等待
                while (size >= capacity) wait();
                // 計算實際可生產量(不超容)
                int inc = (size + left > capacity) ? (capacity - size) : left;
                size += inc;
                left -= inc;
                System.out.printf("%s produce(%3d) --> left=%3d, inc=%3d, size=%3d%n",
                        Thread.currentThread().getName(), val, left, inc, size);
                notifyAll(); // 唤醒消費者
            }
        } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
    }

    public synchronized void consume(int val) {
        try {
            int left = val; // 剩餘待消費量
            while (left > 0) {
                // 倉空等待
                while (size <= 0) wait();
                // 計算實際可消費量(不超賣)
                int dec = (size < left) ? size : left;
                size -= dec;
                left -= dec;
                System.out.printf("%s consume(%3d) <-- left=%3d, dec=%3d, size=%3d%n",
                        Thread.currentThread().getName(), val, left, dec, size);
                notifyAll(); // 唤醒生產者
            }
        } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
    }
}

三、關鍵設計解析

1. synchronized 保證互斥

produce/consume 同步於同一 Depot 實例,任一時刻只有一個線程能修改 size,避免競態條件。

2. while 循環而非 if 判斷條件

while (size >= capacity) wait(); // 正確
// if (size >= capacity) wait(); // 錯誤:虛假喚醒會導致邏輯錯誤

Java 規範允許虛假喚醒,線程可能在無 notify 情況下醒來,必須重新檢查條件。

3. 批量生產/消費邏輯

  • left 變量追蹤「本次請求剩餘量」,支持單次調用跨越多個等待-生產/消費週期。
  • inc/dec 計算實際增減量,邊界保護:min(剩餘請求, 剩餘容量/庫存)。

4. notifyAll() 而非 notify()

  • 多生產者/多消費者場景下,notify() 可能喚醒同類線程(如生產者喚醒生產者),導致無效切換。
  • notifyAll() 喚醒所有等待線程,由條件判斷自動過濾,雖略增上下文切換但邏輯正確。

5. InterruptedException 處理

捕獲後調用 Thread.currentThread().interrupt() 恢復中斷狀態,避免吞噬中斷信號,便於上層協作式取消。

四、運行示例與輸出分析

Depot mDepot = new Depot(100);
Producer mPro = new Producer(mDepot);
Customer mCus = new Customer(mDepot);
mPro.produce(60);   // Thread-0: 生產 60,size=60
mPro.produce(120);  // Thread-4: 生產 40(滿 100),left=70 等待
mCus.consume(90);   // Thread-2: 消費 90,size=10,喚醒生產者
mCus.consume(150);  // Thread-3: 消費 10(空),left=140 等待
mPro.produce(110);  // Thread-1: 生產 90(滿 100),left=20 等待
// ... 交替進行

輸出片段:

Thread-0 produce( 60) --> left= 0, inc= 60, size= 60
Thread-4 produce(120) --> left= 70, inc= 40, size=100
Thread-2 consume( 90) <-- left= 0, dec= 90, size= 10
Thread-3 consume(150) <-- left=140, dec= 10, size= 0
Thread-1 produce(120) --> left= 20, inc=100, size=100
...

五、常見陷阱與進階優化

進階:使用 Condition 替代 wait/notify

class DepotV2 {
    private final Lock lock = new ReentrantLock();
    private final Condition notFull = lock.newCondition();
    private final Condition notEmpty = lock.newCondition();
    // produce() 中:notFull.await(); notEmpty.signal();
    // consume() 中:notEmpty.await(); notFull.signal();
}
  • 精準通知:notEmpty 只喚醒消費者,notFull 只喚醒生產者,減少無效喚醒。
  • 支援多條件、公平鎖、可中斷等待(awaitInterruptibly)、超時等待(awaitNanos)。

六、總結

synchronized + wait/notifyAll 是實現生產者-消費者的基石模式。核心要點:

  1. 共享狀態(size)由同一把鎖保護。
  2. 條件檢查用 while 循環防虛假喚醒。
  3. 狀態改變後 notifyAll() 廣播通知。
  4. 批量操作引入 left 變量處理部分滿足。
  5. 正確處理中斷,保持線程協作可取消性。

實際項目中,優先考慮 BlockingQueue(ArrayBlockingQueue、LinkedBlockingQueue)等成熟組件,它們封裝了上述所有細節,並提供豐富的阻塞/超時/非阻塞 API。手寫 wait/notify 僅用於教學理解或極度定制化場景。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
陷阱後果修正
用 if 替代 while虛假喚醒導致邏輯錯誤堅持 while (condition) wait()
忘記 notifyAll()線程永久等待(死鎖)狀態改變後必須通知
wait() 不在 synchronized 中拋 IllegalMonitorStateException確保持有監視器鎖
吞噬 InterruptedException無法響應關閉信號重設中斷標記或拋出