Mastering Java's Blocking Queues: When to Use Each JUC Queue
This article categorizes the tools in java.util.concurrent, explains the six major groups of JUC utilities, dives deep into the design and behavior of various blocking and non‑blocking queues, shows their core methods, typical use‑cases, and provides code examples for practical implementation.
Introduction
If we roughly classify JUC utilities by purpose and characteristics, the tools can be divided into six categories: executors and thread pools, concurrent queues, synchronization tools, concurrent collections, locks, and atomic variables.
In the concurrency series , we mainly covered executors and thread pools, synchronization tools, and locks. While analyzing source code we also touched on queues, which appear in many forms in JUC. This article gives a high‑level view to quickly understand and differentiate these seemingly chaotic queues.
Concurrent Queues
Java concurrent queues can be divided into two implementation types:
Blocking queues
Non‑blocking queues
The former is based on lock implementation, the latter on CAS non‑blocking algorithms.
Common queues include the following:
Why are there so many kinds of queues? Because, like locks, queues are designed to handle different scenarios, reflecting the Single Responsibility Principle.
Blocking Queues
Blocking queues support two extra operations compared to non‑blocking queues:
Blocking insertion – when the queue is full, the inserting thread blocks until space becomes available.
Blocking removal – when the queue is empty, the removing thread blocks until an element appears.
These behaviors can be summarized in a table.
ArrayBlockingQueue
The name indicates an array‑based implementation; it can be bounded if a capacity is specified in the constructor.
public ArrayBlockingQueue(int capacity, boolean fair) {
if (capacity <= 0)
throw new IllegalArgumentException();
this.items = new Object[capacity];
lock = new ReentrantLock(fair);
notEmpty = lock.newCondition();
notFull = lock.newCondition();
}By default it uses a non‑fair lock. Interview question: why is the default non‑fair lock, what are its advantages and possible drawbacks?
LinkedBlockingQueue
This is a bounded blocking queue whose default (and maximum) capacity is Integer.MAX_VALUE, making it optionally‑bounded.
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
public LinkedBlockingQueue(int capacity) {
if (capacity <= 0) throw new IllegalArgumentException();
this.capacity = capacity;
last = head = new Node<E>(null);
}Array‑based queues often have more predictable performance, while linked queues offer higher insertion/removal efficiency.
PriorityBlockingQueue
An unbounded blocking queue that orders elements according to their natural order or a supplied Comparator.
Null elements are not allowed; elements must be comparable.
Elements with equal priority have undefined ordering unless a secondary rule is provided.
Because it is unbounded, put never blocks; it simply delegates to offer.
public void put(E e) {
offer(e); // never blocks
}The default initial capacity is 11 and grows automatically.
DelayQueue
A unbounded blocking queue that holds elements implementing Delayed. Elements become available after their delay expires.
public long getDelay(TimeUnit unit) {
return unit.convert(time - now(), NANOSECONDS);
}
public int compareTo(Delayed other) {
if (other == this) return 0;
long diff = getDelay(NANOSECONDS) - other.getDelay(NANOSECONDS);
return (diff == 0) ? 0 : (diff < 0 ? -1 : 1);
}Typical use‑cases: cache expiration and scheduled task execution.
SynchronousQueue
A queue that does not store elements; each put must wait for a corresponding take and vice versa.
ExecutorService executor = Executors.newFixedThreadPool(2);
SynchronousQueue<Integer> queue = new SynchronousQueue<>();
Runnable producer = () -> {
Integer e = ThreadLocalRandom.current().nextInt();
try { queue.put(e); } catch (InterruptedException ex) { ex.printStackTrace(); }
};
Runnable consumer = () -> {
try { Integer v = queue.take(); } catch (InterruptedException ex) { ex.printStackTrace(); }
}; Executors.newCachedThreadPool()internally uses a SynchronousQueue because the pool can grow to Integer.MAX_VALUE threads, so tasks are handed off directly without queuing.
LinkedTransferQueue
Provides a transfer method that blocks until the element is consumed. It also offers tryTransfer (non‑blocking) and tryTransfer with timeout.
BlockingQueue blocks when the queue is full; TransferQueue blocks when there is no consumer waiting.
LinkedBlockingDeque
A double‑ended blocking queue based on a linked list, allowing insertion and removal at both head and tail, which reduces contention in concurrent scenarios.
Dual‑ended queues provide an extra entry point, halving contention when multiple threads enqueue simultaneously.
Summary
This article quickly categorizes Java's blocking queues, clarifies their design differences, and explains when to use each. Understanding these queues helps you read source code with confidence and choose the right implementation for your concurrency needs.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Programmer DD
A tinkering programmer and author of "Spring Cloud Microservices in Action"
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
