最簡單的生消模型:一個共享緩衝槽,生產者放入、消費者取出。透過 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 表示滿,簡單明確。 |
notifyAll() 替代 notify():單緩衝區單生產單消費 notify() 足夠,notifyAll() 增加上下文切換開銷。ArrayBlockingQueue(1):JDK 內建執行緒安全隊列,一行代碼替換整個類,生產環境首選。LinkedBlockingQueue),提高吞吐、降低阻塞概率。// 一行代碼替換:有界阻塞隊列
BlockingQueue<String> queue = new ArrayBlockingQueue<>(100);
// 生產者
queue.put(item); // 滿時阻塞
// 費者
String item = queue.take(); // 空時阻塞
優點:無需手動管理鎖、條件變數、虛假喚醒;支援超時、中斷、批量操作;效能經過 JVM 優化。