Java 33: BlockingQueue & Producer-Consumer
easy⏱ 5 mincoursejava
The four operations × three modes
add/offer/put (insert), remove/poll/take (remove), element/peek/- (inspect). Three modes: throws, returns special (null/false), blocks. So queue.put(x) blocks if full; queue.take() blocks if empty. Blocking versions = natural back-pressure.
Choose your queue
ArrayBlockingQueue: fixed capacity, FIFO, low overhead. LinkedBlockingQueue: optionally bounded, higher throughput (separate head/tail locks). SynchronousQueue: 0-capacity — every put waits for a take. Used by newCachedThreadPool. PriorityBlockingQueue: heap-based, unbounded, natural ordering or comparator.
Poison pill shutdown
Signal consumers to stop: put a sentinel value. q.put(POISON); q.put(POISON); ... (one per consumer). Each consumer sees the poison, exits the loop, returns. Cleaner than interrupt for graceful shutdown.
final Object POISON = new Object();
// consumer loop
while (true) {
Object x = q.take();
if (x == POISON) break;
handle(x);
}
Build a bounded queue + back-pressure
Implement BoundedQueue<T>(capacity) with offer (returns false if full), poll (returns null if empty), and an async put that awaits space. Simulate a fast producer + slow consumer — the producer should wait when the queue is full.
Prefer Disruptor for extreme throughput
For >100M ops/sec pipelines (trading, logging), BlockingQueue locks become the bottleneck. The LMAX Disruptor is a ring-buffer-based alternative with no locks and near-zero GC. Overkill for most apps but essential for latency-sensitive ones.
Quiz: Back-pressure via queue
How does a bounded queue provide back-pressure? Producer's put blocks when queue is full — naturally slows down to the consumer's rate. Without back-pressure, fast producers overflow memory. The queue is the flow-control mechanism.