SynchronousQueue的核心机制是线程间“手递手”直接交接,不存储元素,put与take必须同时发生才能完成配对交换;它通过TransferStack(非公平)或TransferQueue(公平)实现阻塞式配对通信,容量恒为0,本质是协调器而非容器。

SynchronousQueue 的核心机制不是靠内部存储,而是靠线程间“手递手”式的直接交接——生产者线程必须等到消费者线程恰好在场,才能把任务(Task)或节点(Node)交出去;反之亦然。它不保存元素,也不缓存任何数据,整个交换过程发生在两个线程的协作点上,本质是阻塞式配对通信。
Task/Node 是配对交换的载体
在 SynchronousQueue 中,“Task”并非特指 Runnable 或 Callable,而是泛指任意要传递的数据对象;而 “Node” 是其内部实现中用于承载该数据、记录线程状态和控制流转的链表节点(基于 Transferer 接口的不同实现,如 TransferStack 或 TransferQueue)。每个 Node 封装了待传递的数据、等待的线程引用、以及匹配状态标记(如 isData 字段区分是数据还是请求)。
- 当线程调用
put(e),会构造一个Node(e, isData=true),表示“我有数据要给出去”,然后尝试与一个正在执行take()的消费者线程配对 - 当线程调用
take(),会构造一个Node(null, isData=false),表示“我要取一个数据”,并寻找正在put的生产者 - 一旦两个 Node 在同一时刻完成 CAS 协作(比如在栈顶或队列头成功匹配),数据就从生产者的 Node 直接写入消费者的 Node,随后双方线程各自返回
无缓冲的关键在于“配对即完成”
没有中间存储意味着:只要配对成功,数据就从一个线程的栈帧/寄存器直接拷贝到另一个线程的局部变量中,全程不经过堆内存中的队列数组或链表暂存。SynchronousQueue 的容量恒为 0,size() 始终返回 0,peek() 永远返回 null——它只是线程同步的协调器,不是容器。
分析 Java Spring Boot 后端 Controller 代码,自动生成 Markdown 格式的接口文档。当用户说“生成 Java 接口文档”、“生成 API 文档”、“从 Spring 代码生成文档”时触发。
- 若无配对线程,
put线程会阻塞在 park 状态,直到有take线程到来并完成匹配 - 同理,
take线程也会阻塞,直到有put线程出现 - 这种“等待-唤醒-交换-返回”的原子闭环,就是“手递手”的实质:数据不落地,只过手
两种实现模式影响交换路径
SynchronousQueue 内部根据公平策略选择不同 Transferer:
立即学习“Java免费学习笔记(深入)”;
-
非公平模式(默认) 使用
TransferStack:用 LIFO 栈结构组织等待线程。后到的生产者可能优先匹配先到的消费者(栈顶优先),适合低延迟场景 -
公平模式 使用
TransferQueue:用 FIFO 队列管理,严格按等待顺序配对,避免饥饿,但吞吐略低 - 无论哪种,Node 都是交换的信封——它携带数据、标识角色、参与 CAS 匹配,但自身不构成缓冲区
典型使用场景印证手递手特性
最常见的是配合 Executors.newCachedThreadPool():线程池的工作队列就是 SynchronousQueue。每当提交一个任务,必须立刻有空闲线程来接手;如果没有,则新建线程。这正体现了“有产必有销、有销必有产”的紧耦合传输逻辑。
- 任务提交线程(Producer)和工作线程(Consumer)通过 Node 在栈/队列中完成一次 CAS 配对
- 任务对象本身从未进入任何队列缓冲区,而是直接从提交方传入执行方的 run() 方法参数中
- 整个过程不涉及内存拷贝或中间队列寻址,延迟极低,但要求双方线程严格协同

















