library
advancedBlockingQueue Implementations
Choose the right BlockingQueue for producer-consumer patterns: ArrayBlockingQueue, LinkedBlockingQueue, or SynchronousQueue.
A BlockingQueue makes the producer-consumer handoff safe without any wait/notify of your own.
BlockingQueue = a sushi conveyor belt. Chefs (producers) put plates on when there's space. Customers (consumers) take plates when available. If the belt is full, chefs wait. If empty, customers wait.
Key Concepts
1
BlockingQueue<Job> q = new ArrayBlockingQueue<>(100);2
// producer
q.put(job); // blocks while the queue is full3
// consumer
Job j = q.take(); // blocks while the queue is empty4
The blocking is the feature. A bounded queue plus put gives you backpressure for free: a producer that
outruns the consumers is slowed down instead of filling memory.
put
5
The four method families, and this table is the whole API.
throws blocks times out returns special
add(e) put(e) offer(e, t, unit) offer(e) -> false
remove() take() poll(t, unit) poll() -> null
element() - - peek() -> null6
The implementations.
ArrayBlockingQueue bounded, one array, a single lock
LinkedBlockingQueue optionally bounded, separate put/take locks
PriorityBlockingQueue unbounded, ordered by priority
SynchronousQueue capacity zero -- a direct handoff
DelayQueue take() waits until an element is due
LinkedTransferQueue transfer() waits for a consumer7
LinkedBlockingQueue has two locks, so a producer and a consumer do not contend. That gives higher
throughput than ArrayBlockingQueue under load; ArrayBlockingQueue allocates nothing per element and has
more predictable latency.
LinkedBlockingQueueArrayBlockingQueue
8
Leave LinkedBlockingQueue unbounded and you have removed the backpressure. It grows until the heap
runs out, which is how a slow consumer becomes an OutOfMemoryError far from the real cause. Always pass a
capacity.
LinkedBlockingQueueOutOfMemoryError
9
SynchronousQueue holds nothing. Every put waits for a take. That is what makes
Executors.newCachedThreadPool() start a new thread per task — the queue can never accept one.
SynchronousQueueputtakeExecutors.newCachedThreadPool()
10
DelayQueue is a scheduler. Elements implement getDelay, and take() returns only expired ones, which
is the natural fit for retries and timeouts.
DelayQueuegetDelaytake()
11
And the queue choice shapes a thread pool. Bounded means tasks get rejected under load and the
RejectedExecutionHandler decides what happens; unbounded means maximumPoolSize is never reached, because
there is always room in the queue.
RejectedExecutionHandlermaximumPoolSize
12
What the interviewer is probing.1. "Why is an unbounded LinkedBlockingQueue risky?" Probing: backpressure. Stalls: "It is
flexible." Moves up: it grows until the heap runs out, so a slow consumer becomes an
OutOfMemoryError far from the real cause; a capacity gives you backpressure instead.
13
2. "What is the difference between offer, put and add?" Probing: the four method families.
Stalls: "They all add." Moves up: add throws when full, offer returns false, offer with a
timeout waits, and put blocks indefinitely.
14
3. "Why does a cached thread pool create unbounded threads?" Probing: SynchronousQueue.
Stalls: "It is configured that way." Moves up: its queue has zero capacity, so every submission
must be handed directly to a thread and the pool starts one whenever none is free.
15
4. "How does queue choice change a ThreadPoolExecutor?" Probing: the interaction people miss.
Stalls: "It does not." Moves up: with an unbounded queue maximumPoolSize is never reached,
because there is always room; a bounded queue is what makes the pool grow and then reject.