synchronousqueue 的 put() 会一直阻塞,直到有线程调用 take(),因为它不存储元素,put() 实际是挂起当前线程等待配对的 take() 线程完成 handoff。

为什么 SynchronousQueue 的 put() 会一直阻塞,直到有线程调用 take()
SynchronousQueue 不是“队列”,它根本没地方存元素。所谓“入队”其实是把线程挂起,等另一个线程来“取”,双方直接 handoff(交接)。这决定了它的核心行为:没有配对操作,put() 就卡住,take() 也卡住。
常见错误现象:put("x") 后程序停住不动,日志断在这一行,CPU 占用低 —— 不是死锁,是正常等待配对;如果只启动生产者没启消费者,它永远等下去。
- 它不缓存任何元素,容量恒为 0,
size()永远返回0,isEmpty()永远返回true - 不支持
peek()、iterator()、toArray()等查看类操作,调用会抛UnsupportedOperationException - 所有阻塞都基于线程协作:一个线程进
put(),必须有另一个线程在同一时刻进take(),才能完成数据传递
offer() 和 poll() 在 SynchronousQueue 中为何几乎总返回 false
offer(E e) 和 poll() 是非阻塞的“试探性”操作。在 SynchronousQueue 中,它们只在“对方线程恰好正在等待”时才成功 —— 这种时机极难捕捉,实际使用中基本等于“运气好才成”。
使用场景有限:仅适合做快速探测(比如判断当前是否有消费者就绪),不能用于可靠投递或消费。
立即学习“Java免费学习笔记(深入)”;
-
offer("x")成功的前提:此刻已有线程阻塞在take()上;否则立刻返回false -
poll()成功的前提:此刻已有线程阻塞在put("y")上;否则立刻返回null - 带超时的
offer(e, timeout, unit)和poll(timeout, unit)更实用些,但本质仍是“等一个配对线程出现”,不是“等数据准备好”
和 LinkedTransferQueue 混用时,为什么握手机制突然失效
看起来都是“传输队列”,但 LinkedTransferQueue 有真实存储能力(内部是链表),transfer() 才模拟握手;而 SynchronousQueue 的握手是强制、无条件的。混用会导致语义错位。
典型坑:把 SynchronousQueue 当作轻量版缓冲队列用,发现吞吐上不去、线程大量阻塞,换成 LinkedTransferQueue 后看似“不卡了”,实则掩盖了生产/消费速率不匹配的问题。
-
SynchronousQueue强制要求生产者和消费者节奏严格对齐;LinkedTransferQueue允许暂存,transfer()成功后仍可继续put() - 两者构造参数不同:
SynchronousQueue不接受容量参数;LinkedTransferQueue构造函数无参,也不设限 - 性能差异明显:
SynchronousQueue握手开销小,但依赖线程调度精度;LinkedTransferQueue多一次节点分配和链表操作,但容错性强
调试时怎么确认是不是被 SynchronousQueue 卡住了
看线程堆栈最直接。JDK 自带工具或 IDE 的线程视图里,如果看到大量线程停在 SynchronousQueue.transfer() 或 AbstractQueuedSynchronizer$ConditionObject.await(),基本就是它。
关键线索是线程状态:WAITING(不是 BLOCKED)且堆栈含 SynchronousQueue 相关方法。
- 用
jstack <pid></pid>抓堆栈,搜索"SynchronousQueue"或"transfer" - 注意区分:如果是
take()卡住,堆栈通常含TransferStack.awaitFulfill();put()卡住则多见TransferStack.transfer() - 不要只看队列是否为空——
size()永远是0,这个值毫无诊断价值
真正难的是配对时机稍纵即逝,比如消费者启动慢了几十毫秒,生产者就卡住;这种竞态不会报错,只会让系统响应变慢或假死。得盯住线程生命周期和启动顺序。








