SynchronousQueue 是无内部容量的同步阻塞队列,容量恒为0,不存储元素,必须由生产者与消费者线程实时配对完成hand-off传递。

SynchronousQueue 是 Java 并发包中一个非常特殊的阻塞队列,它**不存储元素**——每个插入操作必须等待另一个线程的移除操作,反之亦然。它本质上是“手递手”(hand-off)机制的实现:生产者线程把数据直接交给消费者线程,中间不经过任何缓冲,没有队列容量(容量为 0),天然适合一对一、低延迟、高同步要求的场景。
为什么说它是“无内部容量”的单对单传输?
它内部不维护任何容器(数组或链表),不保留待处理元素。调用 put() 的线程会一直阻塞,直到有另一个线程正好在调用 take();同理,take() 也会阻塞,直到有线程正在执行 put()。这种“配对即完成”的语义,决定了它天然是一对一、即时传递、零拷贝(逻辑上)的通信通道。
- 容量恒为 0,
size()始终返回 0,peek()总是 null - 不支持
addAll()、removeIf()等批量操作(无元素可操作) - 构造时不能指定容量(不像 ArrayBlockingQueue 或 LinkedBlockingQueue)
典型用法:两个线程“手递手”传递对象
下面是一个最简但完整的示例,模拟主线程(生产者)向工作线程(消费者)传递一个字符串:
SynchronousQueue<String> queue = new SynchronousQueue<>();
// 消费者线程
new Thread(() -> {
try {
String msg = queue.take(); // 阻塞,等待生产者 put
System.out.println("收到:" + msg);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}).start();
// 生产者线程(主线程)
try {
queue.put("Hello, SynchronousQueue!"); // 阻塞,直到消费者 take
System.out.println("发送完成");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
注意:两线程必须“错开时机”才能完成配对。若先启动消费者再执行 put,或先 put 再启消费者,都会阻塞直至对方就位 —— 这正是“同步握手”的体现。
立即学习“Java免费学习笔记(深入)”;
搭配 ExecutorService 实现动态任务交接
更实用的场景是配合 Executors.newCachedThreadPool()(其默认工作队列就是 SynchronousQueue)来实现“任务来了立刻交到空闲线程手上”:
- 当提交任务时,若存在空闲线程,任务直接交给它执行(相当于一次 hand-off)
- 若无空闲线程,则新建线程(因为队列无法缓存任务)
- 这使得线程数随负载动态伸缩,且任务无排队延迟
你也可以显式使用:
ExecutorService executor = new ThreadPoolExecutor(
0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<>(), // 关键:无缓冲
Executors.defaultThreadFactory()
);
注意事项与适用边界
SynchronousQueue 强依赖线程协作时机,不适合异步解耦或削峰填谷:
- 不要用于“生产快、消费慢”的场景 —— 生产者会无限阻塞,可能引发线程饥饿
- 不适用于多对一或一对多:多个生产者同时
put,只有一个消费者take,其余生产者全阻塞;反之亦然 - 异常处理要严谨:中断或关闭时需确保双方都响应
InterruptedException - 调试困难:因无中间状态,出问题时难以定位是哪边没到位
它不是万能队列,而是为强同步、低延迟、点对点协作而生的轻量级信道。用对了,就是真正的“手递手”;用错了,就是互相等待的死锁温床。



















