In-Depth Comparison of ArrayBlockingQueue and LinkedBlockingQueue in Java
Java’s blocking queues are critical components for implementing producer-consumer patterns in concurrent programming.
1. ArrayBlockingQueue: Implementation Details
- Underlying Data Structure: Backed by an array
Object[]. - Capacity: Fixed at initialization; bounded queue.
- Thread Safety
- Uses a single
ReentrantLockto control both read and write operations. - Two
Conditionvariables:notEmpty(for consumers) andnotFull(for producers).
- Uses a single
- Blocking Behavior: Threads wait on the appropriate condition when the queue is empty/full.
- Performance: Simple implementation, but read/write operations are mutually exclusive.
As of the newest verion, ArrayBlockingQueue continues to use a single lock design. This simplifies the implementation and reduces overhead, but may become a performance bottleneck under high concurrency since producers and consumers cannot proceed in parallel.
2. LinkedBlockingQueue: Implementation Details
- Underlying Data Structure: Based on a singly-linked list.
- Capacity
- Can be explicitly set;
- Defaults to
Integer.MAX_VALUEif not specified.
- Thread Safety
- Uses two separate
ReentrantLockinstancesputLockfor producerstakeLockfor consumers
- Two
Conditionobjects:notEmptyandnotFull, each tied to a different lock. - Queue size is tracked by an
AtomicIntegercount to ensure atomic updates.
- Uses two separate
- Concurrency Advantage: Read and write operations can proceed concurrently without contention.
3. Ensuring Safe Concurrent Access in LinkedBlockingQueue
The producer and consumer operations act on opposite ends of the queue:
- Producer
- Appends a new node to
tail.nextand advances thetailpointer.
- Appends a new node to
- Consumer
- Removes
head.nextand advances theheadpointer.
- Removes
This separation ensures minimal contention. While the queue is empty:
- Both
headandtailpoint to a dummy node withnulldata. - This dummy node is reused until the first real element is inserted.
public LinkedBlockingQueue(int capacity) { if (capacity <= 0) throw new IllegalArgumentException(); this.capacity = capacity; last = head = new Node<E>(null); // dummy node }
Dequeue Operation Example
The technique of self-linking (h.next = h) aids garbage collection by breaking unnecessary references.
private E dequeue() {
Node<E> h = head;
Node<E> first = h.next;
h.next = h; // help GC
head = first;
E x = first.item;
first.item = null;
return x;
}
4. Blocking and Signaling (Condition Variables)
Both queues use Condition variables for signaling between producers and consumers.
| Queue Type | Locking Strategy | Condition Variables |
|---|---|---|
| ArrayBlockingQueue | One shared lock | notEmpty, notFull |
| LinkedBlockingQueue | Two separate locks | notEmpty (bound to takeLock), notFull (bound to putLock) |
5. Summary Comparison
| Feature | ArrayBlockingQueue | LinkedBlockingQueue |
|---|---|---|
| Capacity | Fixed, must be specified | Optional, defaults to Integer.MAX_VALUE |
| Data Structure | Array | Singly-linked list |
| Locking Strategy | Single lock | Separate locks for put / take |
| Concurrency | Lower (read/write mutually exclusive) | Higher (read/write can proceed concurrently) |
| Memory Usage | Lower | Higher (dynamic node allocation) |
| GC Optimization | N/A | Self-linking and nullification |
| Recommended Use Case | Small, high-throughput queue | High-concurrency, large or long-lived queues |