有界緩存是生產者-消費者模式的核心數據結構。本文展示四種實現版本的代碼演進,對比各自優缺點,最終落地生產可用的 BoundedBuffer。
封裝循環數組、指針、計數器,提供線程安全的 doPut/doTake、isFull/isEmpty 基礎操作,所有方法 synchronized 修飾。
public class BaseBoundedBuffer<V> {
private final V[] buf; private int tail, head, count;
public BaseBoundedBuffer(int capacity) { this.buf = (V[]) new Object[capacity]; }
protected synchronized final void doPut(V v) { buf[tail] = v; if (++tail == buf.length) tail = 0; ++count; }
protected synchronized final V doTake() { V v = buf[head]; buf[head] = null; if (++head == buf.length) head = 0; --count; return v; }
public synchronized final boolean isFull() { return count == buf.length; }
public synchronized final boolean isEmpty() { return count == 0; }
}
調用者需自行檢查狀態,隊列滿/空時拋出異常,不阻塞。適用於「寧可失敗不等待」的非關鍵路徑。
public class GrumpyBoundedBuffer<V> extends BaseBoundedBuffer<V> {
public synchronized void put(V v) throws Exception { if (isFull()) throw new Exception("隊列已滿"); doPut(v); }
public synchronized V take() throws Exception { if (isEmpty()) throw new Exception("隊列為空"); return doTake(); }
}
循環檢查條件,不滿足則 Thread.sleep() 釋放 CPU。缺點:延遲不可控(受睡眠粒度影響)、忙等待浪費 CPU、難以精確響應中斷。
public class SleepyBoundedBuffer<V> extends BaseBoundedBuffer<V> {
private static final long SLEEP_GRANULARITY = 2000;
public void put(V v) throws InterruptedException {
while (true) {
synchronized (this) { if (!isFull()) { doPut(v); return; } }
Thread.sleep(SLEEP_GRANULARITY);
}
}
public V take() throws InterruptedException {
while (true) {
synchronized (this) { if (!isEmpty()) return doTake(); }
Thread.sleep(SLEEP_GRANULARITY);
}
}
}
利用對象監視器的 wait() 釋放鎖並掛起線程,notifyAll() 唤醒等待線程。完美解決「等待-通知」機制,無忙等待、響應及時、支持中斷。
public class BoundedBuffer<V> extends BaseBoundedBuffer<V> {
public synchronized void put(V v) throws InterruptedException {
while (isFull()) wait(); // 釋放鎖,進入 WAITING 狀態
doPut(v);
notifyAll(); // 唤醒所有等待線程(消費者)
}
public synchronized V take() throws InterruptedException {
while (isEmpty()) wait();
V v = doTake();
notifyAll(); // 唤醒生產者
return v;
}
}
生產環境推薦使用 ReentrantLock 配合 Condition,支持多條件變量(notFull、notEmpty)、公平鎖、可中斷鎖獲取、超時等待等高級特性:
public class LockBoundedBuffer<V> {
private final V[] buf; private int tail, head, count;
private final Lock lock = new ReentrantLock();
private final Condition notFull = lock.newCondition();
private final Condition notEmpty = lock.newCondition();
public void put(V v) throws InterruptedException {
lock.lockInterruptibly();
try {
while (count == buf.length) notFull.await();
buf[tail] = v;
if (++tail == buf.length) tail = 0;
++count;
notEmpty.signal();
} finally { lock.unlock(); }
}
public V take() throws InterruptedException {
lock.lockInterruptibly();
try {
while (count == 0) notEmpty.await();
V v = buf[head];
buf[head] = null;
if (++head == buf.length) head = 0;
--count;
notFull.signal();
return v;
} finally { lock.unlock(); }
}
}
| 版本 | 機制 | 優點 | 缺點 | 適用場景 |
|---|---|---|---|---|
| Grumpy | 檢查拋異常 | 簡單、無阻塞 | 調用者需重試、高併發下異常頻發 | 非關鍵、可丟棄數據 |
| Sleepy | 輪詢睡眠 | 最終能成功 | 延遲高、CPU 浪費、精度差 | 教學演示、極低併發 |
| BoundedBuffer | wait/notifyAll | 標準阻塞、無忙等待 | 單一條件變量、notifyAll 唤醒過多 | 一般生產環境 |
| Lock+Condition | 顯式鎖+多條件 | 精確唤醒、支持公平/中斷/超時 | 代碼稍繁瑣 | 高併發、高可靠生產環境 |
實際項目中,優先選用 java.util.concurrent 包下成熟組件:ArrayBlockingQueue、LinkedBlockingQueue,無需重複造輪子。