SynchronousQueue 是不存储元素的同步移交队列,内部无容量,put与take必须配对执行,size恒为0,适用于零缓冲的线程间直接任务交接。

SynchronousQueue 是 Java 并发包(java.util.concurrent)中一种特殊的阻塞队列,它的核心设计目标就是不存储元素——也就是说,它没有内部容量,不能缓存任何待入队的元素。它只起到“传递”的作用:一个线程必须等待另一个线程来接收(出队),才能完成入队;反之,出队线程也必须等待另一个线程来放入(入队),才能完成出队。
为什么说它“不存储元素”?
不同于 ArrayBlockingQueue 或 LinkedBlockingQueue,SynchronousQueue 内部不维护任何元素数组或链表。它底层使用两种实现机制(取决于构造参数):
-
公平模式(Fair):基于
TransferQueue(无锁双向链表 + CLH 队列),按 FIFO 顺序匹配生产者与消费者; -
非公平模式(默认):基于
TransferStack(无锁栈结构),采用 LIFO 匹配策略,性能略高但不保证等待顺序。
无论哪种实现,其核心逻辑都是:put() 必须等到 take() 同时发生,反之亦然。如果没匹配上,调用线程就会被挂起(park),直到有配对线程出现。
关键行为:每次操作都需“配对”
这是理解它“不存储”的关键。以下操作均会阻塞,直到对方配合:
-
queue.put(item):当前线程阻塞,直到另一个线程执行queue.take(); -
queue.take():当前线程阻塞,直到另一个线程执行queue.put(item); -
queue.offer(item, timeout, unit)或queue.poll(timeout, unit):支持超时,避免无限等待; -
queue.size()永远返回 0(因为它真的不持有任何元素); -
queue.isEmpty()总是返回 true。
典型使用场景:手递手式任务交接
它最适合用于需要直接移交、零缓冲、强协作的场景,比如:
- 工作窃取线程池(如 ForkJoinPool 的默认队列):任务不排队,直接交给空闲线程执行;
- 生产者-消费者严格一对一通信:例如 GUI 事件分发器把事件立即交到处理线程;
-
简化线程间同步握手:替代手动使用
CountDownLatch或Exchanger实现简单信号传递。
示例片段:
ExecutorService exec = new ThreadPoolExecutor(
1, 1, 0L, TimeUnit.SECONDS,
new SynchronousQueue() // 不缓存任务,来了就交,没线程就等
);
此时提交的任务不会排队,若线程正忙,submit() 会阻塞,直到前一个任务完成、线程空闲并准备好接收下一个。
注意:别误当普通队列用
由于它不存数据,以下做法容易出问题:
- 调用
put()前未确保有消费者在等待 → 线程永久阻塞(除非用带超时的版本); - 期望通过
peek()或遍历查看内容 → 所有相关方法都返回null或抛异常; - 在单线程里反复
put和take→ 会死锁(自己无法和自己配对)。
它不是“轻量级队列”,而是“同步移交点”。用对了,高效无锁;用错了,就是隐蔽的阻塞源。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











