Compare ArrayBlockingQueue and LinkedBlockingQueue. When would you choose each?
answer
- Array = fixed array, single lock, always bounded
- Linked = nodes, two locks (put/take), optionally bounded
- Two locks → parallel put+take → throughput
- Default LinkedBlockingQueue is UNBOUNDED → OOM risk
- Array has optional fairness + less GC
basics
~20 sArrayBlockingQueue is backed by a fixed-size array and is always bounded. LinkedBlockingQueue uses linked nodes and is optionally bounded (unbounded by default). ArrayBlockingQueue uses one lock; LinkedBlockingQueue uses two (put and take), so it often has higher throughput under contention.
solid answer
~50 sBoth are FIFO. ArrayBlockingQueue wraps a fixed circular array, so its capacity is set at construction and never changes — it's always bounded and allocates its storage up front. LinkedBlockingQueue is a linked-node queue, optionally bounded (capacity defaults to Integer.MAX_VALUE, effectively unbounded), allocating nodes on demand. The key concurrency difference: ArrayBlockingQueue guards the whole queue with a single ReentrantLock, so puts and takes contend with each other; LinkedBlockingQueue uses two separate locks (a putLock and a takeLock), letting a producer and consumer proceed in parallel, which raises throughput under contention. Trade-offs: prefer ArrayBlockingQueue when you want a tight, predictable memory bound and lower per-element overhead (no node objects, no GC churn); prefer LinkedBlockingQueue for higher concurrent throughput or when you genuinely want unbounded — but an unbounded one risks OutOfMemoryError if producers outrun consumers. ArrayBlockingQueue also offers an optional fairness flag for FIFO lock acquisition.
code
java · 8 lines// Bounded array queue: capacity fixed at construction, single lock, optional fairness
BlockingQueue<Task> aq = new ArrayBlockingQueue<>(1000, /*fair=*/ true);
// Linked queue, EXPLICITLY bounded for backpressure (two-lock, higher throughput)
BlockingQueue<Task> lqBounded = new LinkedBlockingQueue<>(1000);
// DANGER: unbounded by default — producers outpacing consumers can OOM
BlockingQueue<Task> lqUnbounded = new LinkedBlockingQueue<>();go deeper
Knows both are FIFO; one is array-backed and bounded, the other linked and can be unbounded.
Explains the single-lock vs two-lock difference, the unbounded default of LinkedBlockingQueue, and basic memory/throughput trade-offs.
Chooses deliberately for backpressure and footprint, knows the GC implications and the newFixedThreadPool OOM pitfall, uses fairness when starvation matters.
Reasons about lock contention profiles under real workloads, sizes capacity as a system-stability parameter, and weighs these against lock-free/reactive alternatives for the throughput target.
## Both are FIFO BlockingQueues Both implement `BlockingQueue<E>` and order elements **first-in-first-out** (the head is the element that's been waiting longest). They differ in internal structure and locking, which drives the choice. ## ArrayBlockingQueue - **Backed by a fixed-size array** used as a *circular buffer* (two indices, `putIndex` and `takeIndex`, wrap around). - **Always bounded**: you must pass a capacity to the constructor and it cannot change. Storage is allocated **up front** as one array. - **Single lock**: one `ReentrantLock` protects the whole queue, with two `Condition`s (`notEmpty`, `notFull`) for waiting consumers/producers. Because there's one lock, a `put` and a `take` **cannot run simultaneously** — they serialize. - **Optional fairness**: the constructor takes a `fair` boolean; when true, threads waiting on the lock are served in FIFO order (avoids starvation at some throughput cost). - **Lower per-element overhead**: no wrapper node objects, so less memory and **less GC pressure**; memory footprint is fixed and predictable. ## LinkedBlockingQueue - **Backed by linked nodes** (a singly linked list); each element is wrapped in a `Node` object allocated on demand. - **Optionally bounded**: the no-arg constructor sets capacity to `Integer.MAX_VALUE` — *effectively unbounded*. You can pass a capacity to bound it. - **Two locks** (the 'two-lock queue' design): a `putLock` guards the tail, a `takeLock` guards the head. A producer and a consumer can therefore operate **in parallel** on opposite ends, raising throughput when both sides are busy. A `count` is kept as an `AtomicInteger` because the two locks each touch it. - **More overhead**: node allocation per element means more objects and **GC churn**; the head/tail are not pre-allocated. ## How to choose | Want… | Choose | |---|---| | Hard, predictable memory bound; minimal overhead/GC | **ArrayBlockingQueue** | | Maximum throughput with concurrent producers AND consumers | **LinkedBlockingQueue** (two locks) | | Genuinely unbounded buffer | **LinkedBlockingQueue** (default) — but beware OOM | | Fair (FIFO) waiter ordering | **ArrayBlockingQueue** (fair=true) | ## The unbounded trap The biggest practical gotcha: `new LinkedBlockingQueue<>()` is unbounded. If producers are faster than consumers, the queue grows without limit until the JVM throws `OutOfMemoryError`. This is also why `Executors.newFixedThreadPool` (which uses an unbounded LinkedBlockingQueue) can hide a backlog that eventually OOMs. For production backpressure, give it an explicit capacity or use ArrayBlockingQueue. ## Summary ArrayBlockingQueue = bounded array, single lock, low overhead, predictable memory, optional fairness. LinkedBlockingQueue = linked nodes, optionally bounded, dual-lock for parallel put/take throughput, more GC. Reach for ArrayBlockingQueue when bounds and footprint matter; LinkedBlockingQueue when throughput or an unbounded buffer is the goal.
- Why can LinkedBlockingQueue achieve higher throughput than ArrayBlockingQueue under contention?It uses two separate locks (putLock and takeLock) so a producer at the tail and a consumer at the head can operate concurrently, whereas ArrayBlockingQueue's single lock serializes puts and takes.
- What's risky about Executors.newFixedThreadPool's queue?It backs the pool with an unbounded LinkedBlockingQueue, so a flood of submitted tasks queues without limit and can exhaust memory instead of being rejected.
saying these in an interview costs you the question
- Saying LinkedBlockingQueue is always unbounded — it can take a capacity
- Claiming both use a single lock — Linked uses two
- Forgetting the OOM risk of the default unbounded LinkedBlockingQueue
- Assuming ArrayBlockingQueue can grow its capacity