SynchronousQueue is one of the most unintuitive queues in the JDK until the right mental model clicks.
It is called a queue, but it has:
- no real storage capacity
That means every insert must rendezvous with a remove.
This makes it the direct-handoff queue. Nothing waits inside it as buffered backlog.
Problem Statement
Sometimes a system does not want queueing at all.
It wants:
- a producer to hand work directly to a consumer
- and if no consumer is ready, the producer should wait or fail
This is a very different design from:
- buffer up to 100 items
- let consumers catch up later
If your design goal is immediate transfer rather than storage, SynchronousQueue becomes interesting.
Mental Model
SynchronousQueue is:
- zero-capacity
- blocking
- direct handoff
So:
putwaits until another thread is ready to taketakewaits until another thread is ready to put
There is no backlog inside the queue itself.
This is why it is so useful in some executor designs:
- do not queue
- either hand off directly to a worker or force another response such as creating a new worker or rejecting
Runnable Example
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
public class SynchronousQueueDemo {
public static void main(String[] args) throws Exception {
SynchronousQueue<String> queue = new SynchronousQueue<>();
Thread producer = new Thread(() -> {
try {
System.out.println("Producer waiting to hand off");
queue.put("task-1");
System.out.println("Producer completed handoff");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}, "producer");
Thread consumer = new Thread(() -> {
try {
TimeUnit.MILLISECONDS.sleep(300);
System.out.println("Consumer received " + queue.take());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}, "consumer");
producer.start();
consumer.start();
producer.join();
consumer.join();
}
}
The queue never stores task-1 in a backlog.
It only transfers it when both sides meet.
Where It Fits Well
Strong fits:
- direct handoff between producer and worker
- executor designs that should not queue work internally
- systems where buffering is undesirable or misleading
One famous place this matters is cached-thread-pool style behavior:
- if an idle worker is available, hand off work
- otherwise create or reject according to executor policy
That is much closer to direct transfer than to ordinary queue buffering.
Where It Does Not Fit
Poor fits:
- systems that need backlog absorption
- producer-consumer pipelines where buffering is useful
- workloads that need queue introspection or sizing
If you want:
- “accept work now and process it later”
then SynchronousQueue is the wrong primitive.
Its point is:
- “do not accept unless someone can take it now”
Common Mistakes
Thinking it is just a very small queue
It is not a size-1 queue. It is a size-0 handoff point.
Expecting queue depth metrics
There is no meaningful backlog to inspect inside the queue.
Using it without understanding the system’s fallback behavior
If a producer cannot hand off immediately, what should happen:
- wait
- time out
- spawn more workers
- reject
That policy is the real operational design.
Testing and Debugging Notes
Validate:
- producer blocks until consumer is ready
- consumer blocks until producer is ready
- timeout behavior if using timed operations
Operationally, if a system using SynchronousQueue struggles, inspect:
- worker availability
- thread pool saturation
- rejection behavior
The failure mode is not queue growth. It is inability to match producers to consumers quickly enough.
Decision Guide
Use SynchronousQueue when:
- direct handoff is the design goal
- buffering should be avoided
- the producer should feel immediate lack of consumer capacity
Do not use it when:
- you need backlog
- you need bounded buffering
- consumers should drain queued work later
Key Takeaways
SynchronousQueueis a zero-capacity direct-handoff queue.- Producers and consumers rendezvous; no backlog is stored inside the queue.
- It is useful when queueing is undesirable and immediate transfer is the real design.
- If you need buffering, choose another queue type.
Next post: Producer Consumer with BlockingQueue in Java
Comments