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

Java 單緩衝區生產者-消費者模式完整實作與解析

     編輯:Java 相關知識

經典併發模型:單緩衝區生產者-消費者

最簡單的生消模型:一個共享緩衝槽,生產者放入、消費者取出。透過 wait/notify 實現線程協作,展示監視器模式核心機制。

完整程式碼

public class SingleBufferProducerConsumer {

    // 共享緩衝區,null 代表空
    private static Object buffer = null;

    // 三把鎖:互斥鎖、消費者條件、生產者條件
    private static final Object MUTEX = new Object();
    private static final Object CONSUMER_COND = new Object();
    private static final Object PRODUCER_COND = new Object();

    public static void main(String[] args) {
        Thread producer = new Thread(() -> {
            for (int i = 0; ; i++) {
                // 1. 模擬生產耗時
                sleep(1000);
                String item = "item-" + i;
                System.out.println("[生產者] 生產: " + item);

                // 2. 等待緩衝區變空
                synchronized (PRODUCER_COND) {
                    while (buffer != null) {
                        waitFor(PRODUCER_COND);
                    }
                }

                // 3. 放入緩衝區(需互斥)
                synchronized (MUTEX) {
                    buffer = item;
                    System.out.println("[生產者] 放入緩衝區: " + item);
                }

                // 4. 通知消費者
                synchronized (CONSUMER_COND) {
                    CONSUMER_COND.notify();
                }
            }
        }, "Producer");

        Thread consumer = new Thread(() -> {
            for (;;) {
                // 1. 等待緩衝區有資料
                synchronized (CONSUMER_COND) {
                    while (buffer == null) {
                        waitFor(CONSUMER_COND);
                    }
                }

                // 2. 取出資料(需互斥)
                String item;
                synchronized (MUTEX) {
                    item = (String) buffer;
                    buffer = null;
                    System.out.println("  [消費者] 取出: " + item);
                }

                // 3. 模擬消費耗時
                sleep(500);
                System.out.println("  [消費者] 消費完成: " + item);

                // 4. 通知生產者
                synchronized (PRODUCER_COND) {
                    PRODUCER_COND.notify();
                }
            }
        }, "Consumer");

        consumer.start();
        producer.start();
    }

    private static void sleep(long ms) {
        try { Thread.sleep(ms); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
    }

    private static void waitFor(Object lock) {
        try { lock.wait(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
    }
}

核心同步邏輯圖解

生產者                          消費者
  │                               │
  ├─ 生產 item (1s)               │
  │                               │
  ├─ synchronized(PRODUCER_COND)  │
  │   while(buffer != null) wait()│
  │                               │
  ├─ synchronized(MUTEX)          │
  │   buffer = item               │
  │                               │
  ├─ synchronized(CONSUMER_COND)  │
  │   notify()                    │
  │              │                │
  │              ▼                │
  │        synchronized(CONSUMER_COND)
  │        while(buffer == null) wait()
  │              │                │
  │              ▼                │
  │        synchronized(MUTEX)
  │        item = buffer; buffer=null
  │              │                │
  │              ▼                │
  │        消費 (500ms)           │
  │              │                │
  │              ▼                │
  │        synchronized(PRODUCER_COND)
  │        notify()               │
  │              │                │
  └──────────────┴────────────────┘ (循環)

關鍵設計點解析

設計說明
三把鎖分離MUTEX 保護緩衝區讀寫;CONSUMER_COND / PRODUCER_COND 分別作為條件變數,避免虛假喚醒干擾互斥邏輯。
while 而非 if防止虛假喚醒,醒來後必須重新檢查條件。
通知在釋放鎖後先 synchronized(MUTEX) 修改緩衝區並退出,再 notify(),減少鎖爭用。
單緩衝區語意緩衝區為 null 表示空,非 null 表示滿,簡單明確。

常見錯誤與改進

  • ❌ 在同一把鎖上 wait/notify:會導致生產者與消費者互相阻塞,無法並發。
  • ❌ 用 notifyAll() 替代 notify():單緩衝區單生產單消費 notify() 足夠,notifyAll() 增加上下文切換開銷。
  • ✅ 改用 ArrayBlockingQueue(1):JDK 內建執行緒安全隊列,一行代碼替換整個類,生產環境首選。
  • ✅ 多緩衝區:實際系統通常用有界隊列(如 LinkedBlockingQueue),提高吞吐、降低阻塞概率。

現代替代方案

// 一行代碼替換:有界阻塞隊列
BlockingQueue<String> queue = new ArrayBlockingQueue<>(100);

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

// 費者
String item = queue.take(); // 空時阻塞

優點:無需手動管理鎖、條件變數、虛假喚醒;支援超時、中斷、批量操作;效能經過 JVM 優化。

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