synchronousqueue 的 size() 总是 0,因为它不存储元素,仅作为线程间直接交接的同步点,put/take 必须配对阻塞等待,无缓冲区,故 size() 恒为 0、isempty() 恒为 true,peek() 和 iterator() 均抛 unsupportedoperationexception。

为什么 SynchronousQueue 的 size() 总是 0?
因为 SynchronousQueue 根本不存数据——它不是“队列”,而是“交接点”。你调用 put(),线程就卡住,直到另一个线程正好在调 take();反过来也一样。中间没有缓冲区,没有数组,没有链表节点存着你的元素。所以 size() 永远返回 0,isEmpty() 永远是 true,连 peek() 和 iterator() 都直接抛 UnsupportedOperationException。
- 别用
offer(e, timeout, unit)期望“尽力插入”——它和put(e)行为一致:超时前必须等到消费者,否则返回false - 别把它当普通队列去遍历、检查、预判容量——所有这类操作都无效或抛异常
- 调试时看到
size == 0别慌,这是正常态,不是 bug
newCachedThreadPool 为什么非它不可?
Executors.newCachedThreadPool() 内部用的就是 SynchronousQueue,核心就一条:任务来了,有空闲线程就立刻交过去;没有,就新建线程。它不希望任务排队等——等就意味着延迟、积压、内存占用。
- 如果换成
LinkedBlockingQueue(哪怕 capacity=1),任务就会先入队,再等线程来取,破坏“直交”语义 - 一旦线程池最大线程数设得过高 + 队列有缓冲,突发流量可能撑爆堆内存;而
SynchronousQueue强制把背压转成拒绝策略(如AbortPolicy),更可控 - 注意:它的非公平默认模式(
TransferStack)可能导致新任务优先被最新创建的空闲线程抢走,而不是等待最久的线程——对低延迟敏感场景反而更优
自己 new SynchronousQueue 时 fair 参数怎么选?
构造时传 true 就是公平模式(用 TransferQueue),false 或无参就是非公平(TransferStack)。这不是“好不好”的问题,而是“要不要保序”。
- 公平模式:按等待顺序匹配,先
put的等得久,优先被后续take接走;适合需要确定性调度的场景,比如信号量式同步、严格 FIFO 的事件分发 - 非公平模式:后到的
put可能立刻匹配上前一个刚take完正在自旋的线程,吞吐略高,但顺序不可预测;newCachedThreadPool默认用它,因为任务本身无序,快比稳重要 - 公平 ≠ 更安全,也不等于更“正确”;只是多一次 CAS 比较 + 队列维护开销,压测下吞吐可能低 5–10%
常见误用:当成带缓冲的队列来用
最典型的错误是写这种逻辑:queue.offer(task) || fallbackToDisk(),以为 offer() 失败才走备选——但 SynchronousQueue.offer() 只有在没线程等着 take() 时才返回 false,且不阻塞。这跟 put() 的“必须等到”完全不同,极易引发逻辑错乱。
- 想“有空就交,没空就丢/缓存”,应该用
offer(e, 0, TimeUnit.NANOSECONDS),而非无参offer() - 千万别在单线程里先
put()再take()——必然死锁,因为没人配合交接 - 监控时发现大量线程 BLOCKED 在
put()或take(),第一反应不该是扩容,而是查消费者线程是否挂了、阻塞了、或根本没启动










