生產者-消費者模式是併發編程中最基礎、最高頻的協作模式。核心思想:**解耦「生產」與「消費」邏輯,引入「緩衝區/倉庫」作為中介,通過同步機制協調雙方速度差異**。本文將從核心模型、wait/notify 經典實現、阻塞隊列現代實現、進階擴展四個維度深度講解。
put()/take(),內部協調同步與通知。關鍵同步點:
class Warehouse {
private final String[] items;
private int size = 0; // 當前數量
private final int capacity;
public Warehouse(int capacity) {
this.capacity = capacity;
this.items = new String[capacity];
}
// 生產
public synchronized void put(String item) throws InterruptedException {
while (size == capacity) { // 必須用 while 防虛假喚醒
wait(); // 釋放鎖,進入等待隊列
}
items[size++] = item;
System.out.println(Thread.currentThread().getName() + " 生產: " + item + " | 庫存: " + size);
notifyAll(); // 喚醒所有等待線程 (消費者)
}
// 消費
public synchronized String take() throws InterruptedException {
while (size == 0) {
wait();
}
String item = items[--size];
items[size] = null; // 幫助 GC
System.out.println(Thread.currentThread().getName() + " 消費: " + item + " | 庫存: " + size);
notifyAll(); // 喚醒生產者
return item;
}
}
class Producer implements Runnable {
private final Warehouse warehouse;
public Producer(Warehouse w) { this.warehouse = w; }
@Override public void run() {
for (int i = 0; i < 10; i++) {
try { warehouse.put("Product-" + i); Thread.sleep(100); }
catch (InterruptedException e) { Thread.currentThread().interrupt(); break; }
}
}
}
class Consumer implements Runnable {
private final Warehouse warehouse;
public Consumer(Warehouse w) { this.warehouse = w; }
@Override public void run() {
for (int i = 0; i < 10; i++) {
try { warehouse.take(); Thread.sleep(150); }
catch (InterruptedException e) { Thread.currentThread().interrupt(); break; }
}
}
}
// 測試
public static void main(String[] args) {
Warehouse wh = new Warehouse(5); // 容量 5
new Thread(new Producer(wh), "生產者-1").start();
new Thread(new Producer(wh), "生產者-2").start();
new Thread(new Consumer(wh), "消費者-1").start();
new Thread(new Consumer(wh), "消費者-2").start();
}
| 細節 | 錯誤寫法 | 正確寫法 | 原因 |
|---|---|---|---|
| 等待條件判斷 | if (full) wait(); | while (full) wait(); | 防止虛假喚醒 (spurious wakeup) 導致邏輯錯誤 |
| 喚醒方式 | notify() | notifyAll() | 多生產/多消費場景下,notify() 可能喚醒同類線程導致信號丟失/死鎖 |
| 鎖對象 | 不同對象 | 同一對象 (this/倉庫實例) | wait/notify 必須在同一監視器對象上 |
| 中斷處理 | 忽略/吞沒 | 捕獲 InterruptedException → Thread.currentThread().interrupt() 保留中斷狀態 |
JDK 1.5+ java.util.concurrent.BlockingQueue 完美封裝了「有界緩衝區 + 阻塞 put/take + 線程安全」,**無需手寫同步代碼**。
BlockingQueue warehouse = new ArrayBlockingQueue<>(5); // 容量 5
// 生產者
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
warehouse.put("Product-" + i); // 滿則阻塞
System.out.println("生產: Product-" + i);
Thread.sleep(100);
} catch (InterruptedException e) { Thread.currentThread().interrupt(); break; }
}
}).start();
// 消費者
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
String item = warehouse.take(); // 空則阻塞
System.out.println("消費: " + item);
Thread.sleep(150);
} catch (InterruptedException e) { Thread.currentThread().interrupt(); break; }
}
}).start();
| 隊列 | 特性 | 適用場景 |
|---|---|---|
ArrayBlockingQueue | 有界、數組、FIFO、公平鎖可選 | 固定容量生產消費、內存可控 |
LinkedBlockingQueue | 可選有界、鏈表、吞吐高 | 無界/大容量緩衝、日誌異步寫入 |
PriorityBlockingQueue | 無界、優先級排序 | 任務優先級調度 |
DelayQueue | 無界、延遲執行 | 定時/延遲任務 |
SynchronousQueue | 容量 0、直接傳遞 | 線程池 CachedThreadPool、交換站模式 |
offer(item, timeout, unit):超時放入,失敗返回 false (不阻塞/可控等待)。poll(timeout, unit):超時取出,超時返回 null。put/take:無限阻塞,核心生產消費語義。remainingCapacity() / size():監控指標。
final String POISON = "POISON_PILL";
// 生產者結束前放入 N 個毒丸 (N = 消費者數量)
for (int i = 0; i < consumerCount; i++) warehouse.put(POISON);
// 消費者
while (true) {
String item = warehouse.take();
if (POISON.equals(item)) break; // 收到毒丸退出
process(item);
}
Reactive Streams (Project Reactor/RxJava) 以 Flux/Flowable 實現生產者-消費者,**背壓機制**自動協調速度差,替代手動阻塞隊列。
wait/notify 版本,**必須掌握 while 循環、notifyAll、中斷處理**。BlockingQueue (ArrayBlockingQueue/LinkedBlockingQueue),標準庫經過極致優化、經受海量生產驗證。notify() (除非單生產單消費)、自定義鎖/條件 (除非極特殊性能需求)、忽略中斷異常。生產者-消費者模式的本質是**「共享緩衝區 + 狀態依賴同步」**。從 synchronized/wait/notify 到 BlockingQueue 再到分佈式消息隊列,形式在變,核心不變。掌握其演進脈絡,才能在不同層級、不同規模的系統中游刃有餘地解決併發協作問題。