LinkedBlockingQueue vs ArrayBlockingQueue — Locking Strategy, Bounds, and Throughput
Fast recall
Quick revision
Core answer
BlockingQueue for producer-consumer work, but they differ in internal structure and locking strategy. `ArrayBlockingQueue` is backed by a fixed-size circular array with a single reentrant lock shared by producers and consumers — simple, predictable, slight preference for the fair-lock option. `LinkedBlockingQueue` uses a linked-node structure with two separate locks (one for put, one for take) allowing producers and consumers to operate concurrently — higher throughput when both are busy. LinkedBlockingQueue is optionally bounded (default is unbounded, which is a landmine), whereas ArrayBlockingQueue is always bounded. Rules of thumb: use ArrayBlockingQueue when you want strict fixed capacity and lower memory overhead; use LinkedBlockingQueue(capacity) for higher throughput under balanced load. Never use `new LinkedBlockingQueue<>()` without a capacity in production — the unbounded default causes OOM.Whether the candidate knows the locking-model difference (single vs two locks) as the root of the throughput difference, and the bounded/unbounded distinction
Using `new LinkedBlockingQueue<>()` with no capacity in production. Not knowing ArrayBlockingQueue's single-lock model limits throughput under heavy contention
Mention the exact locking structure: ABQ has one `ReentrantLock` + two conditions (notEmpty, notFull); LBQ has two ReentrantLocks (putLock, takeLock) each with its own condition, allowing concurrent put+take. Note fairness option on ABQ and ScheduledThreadPoolExecutor's use of DelayQueue
Interview-ready guide
Interview answer
Both implement BlockingQueue, but they differ in structure and locking.
`ArrayBlockingQueue`
- Backed by a fixed-size circular array
- Capacity is mandatory and fixed at construction
- Uses one `ReentrantLock` shared by producers and consumers, with two conditions (notEmpty, notFull)
- Optionally fair — FIFO ordering of waiters (throughput cost)
- Minimal per-element memory overhead — just an array slot
`LinkedBlockingQueue`
- Backed by a linked list of nodes, one node per element
- Capacity is optional — default is
Integer.MAX_VALUE(unbounded!) - Uses two `ReentrantLock`s —
putLockandtakeLock - Producer and consumer can operate simultaneously because they hold different locks
- No fairness option
- Higher per-element memory cost (extra node objects) and more GC pressure
Why the two-lock design wins
- ABQ: put and take serialize on the same lock — one thread at a time
- LBQ: put and take use different locks — one producer and one consumer run in parallel
- Under balanced load, LBQ typically shows 1.5–3× higher throughput
- When producers and consumers are imbalanced (mostly producers or mostly consumers), the difference shrinks
The unbounded `LinkedBlockingQueue` landmine
new LinkedBlockingQueue<>()with no capacity defaults to ~2 billionExecutors.newFixedThreadPool(N)uses exactly this internally- Under sustained overload: queue grows, heap fills with queued task state, eventually OOM
- Always specify a capacity in production — or use
ArrayBlockingQueue, which forces you to
Fairness
- Only
ArrayBlockingQueueoffers it new ArrayBlockingQueue<>(1000, true)— FIFO ordering of waiters- Costs ~5–10× in throughput but prevents starvation
- Worth it for latency-sensitive, real-time-ish workloads
When I pick each
ArrayBlockingQueue: strict bounded capacity, fair ordering, minimal memory, predictable latency, smaller queuesLinkedBlockingQueue(capacity): balanced producer-consumer, higher throughput, larger queues — but always with explicit capacity- Modern alternative:
LinkedTransferQueue— lock-free, high throughput, but unbounded (needs upstream rate limiting)
Other BlockingQueue variants worth mentioning
PriorityBlockingQueue— unbounded priority heapDelayQueue— delayed elements; used byScheduledThreadPoolExecutorSynchronousQueue— zero capacity, direct handoff; used byCachedThreadPool
Recommendation: For most production work queues in a thread pool, use `LinkedBlockingQueue(capacity)` for the two-lock throughput advantage. Reach for `ArrayBlockingQueue` when you want mandatory bounds (the API prevents unbounded mistakes), need fairness, or want minimal GC pressure from a predictable small queue. Never ship new LinkedBlockingQueue<>() without a capacity — the unbounded default has caused enough production outages that it should be treated as a code-review blocker. If you're on Java 7+ and looking for maximum throughput, benchmark LinkedTransferQueue — its lock-free design often beats both under heavy contention.
`LinkedBlockingQueue` vs `ArrayBlockingQueue` — Locking Strategy, Bounds, and Throughput
Direct Answer
Both implement BlockingQueue for producer-consumer work, but they differ in internal structure and locking strategy. ArrayBlockingQueue is backed by a fixed-size circular array with a single reentrant lock shared by producers and consumers — simple, predictable, slight preference for the fair-lock option. LinkedBlockingQueue uses a linked-node structure with two separate locks (one for put, one for take) allowing producers and consumers to operate concurrently — higher throughput when both are busy. LinkedBlockingQueue is optionally bounded (default is unbounded, which is a landmine), whereas ArrayBlockingQueue is always bounded. Rules of thumb: use ArrayBlockingQueue when you want strict fixed capacity and lower memory overhead; use LinkedBlockingQueue(capacity) for higher throughput under balanced load. Never use new LinkedBlockingQueue<>() without a capacity in production — the unbounded default causes OOM.
Common Mistakes & Pitfalls
Using new LinkedBlockingQueue<>() with no capacity in production. Not knowing ArrayBlockingQueue's single-lock model limits throughput under heavy contention
Check your understanding
Answer these without looking back at the article.
- Why does `LinkedBlockingQueue` default to an unbounded queue?
- What's the practical impact of `ArrayBlockingQueue`'s fair mode?
- When would `SynchronousQueue` be preferable to both?
- How does `LinkedTransferQueue` achieve lock-free operation?
- Why does `Executors.newFixedThreadPool` use `LinkedBlockingQueue` internally?
Related interview prep categories
2Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.