公平模式下SynchronousQueue使用TransferQueue实现FIFO调度,吞吐量比非公平模式低15–20%,因CAS冲突多、缓存不友好;非公平模式用TransferStack支持自旋匹配与LIFO操作,吞吐更高但可能引发线程饥饿。

公平模式下 SynchronousQueue 的线程调度行为
公平模式启用后,SynchronousQueue 内部使用 TransferQueue 结构,所有等待的生产者和消费者线程按 FIFO 顺序排队。这意味着:一个线程调用 put() 后,必须等到排在它前面的所有 take() 请求都被满足,才能匹配成功;反之亦然。
这种顺序性带来两个直接后果:
- 线程不会“插队”,避免了非公平模式下常见的局部性偏好(比如刚唤醒的消费者立刻又抢到下一个生产者)
- 但每次匹配都需遍历等待队列头部,且锁竞争更集中——尤其在高并发下,
ReentrantLock的公平锁开销会抬高 CAS 失败率
实测中,8 线程压测场景下,公平模式吞吐量稳定在约 100万操作/秒,比非公平模式低 15–20%。
非公平模式为何默认吞吐更高
非公平模式(即默认构造)使用 TransferStack,内部是 LIFO 栈结构,节点类型为 SNode,匹配逻辑依赖 match 字段与 waiter 线程引用。它的关键优化点在于:
- 新到来的线程优先尝试“自旋匹配”——即不立即入栈,而是检查栈顶是否已有等待反向操作的线程(如
put遇到take) - 匹配失败才压栈,且出栈总是取栈顶,减少链表遍历开销
- 避免了公平锁强制的线程调度切换,减少了上下文切换和锁重入延迟
这也解释了为什么非公平模式在突发流量下响应更快,但可能让某些长期等待的线程迟迟得不到服务——例如一个慢速消费者持续阻塞,而新来的快速消费者总被优先匹配。
如何验证当前 SynchronousQueue 是公平还是非公平
不能靠 toString() 或 size() 判断,因为两者都返回 0 且 isEmpty() 恒为 true。可靠方式只有两种:
- 构造时显式传参:
new SynchronousQueue(true)表示公平,new SynchronousQueue()或new SynchronousQueue(false)为非公平 - 反射检查内部
transferer字段类型:transferer instanceof TransferQueue→ 公平;transferer instanceof TransferStack→ 非公平(注意:该字段为 package-private,仅限同包或通过Unsafe访问)
别依赖 peek() 或 iterator(),它们在 SynchronousQueue 中一律抛 UnsupportedOperationException。
吞吐量差异背后的真实瓶颈点
差异主要不在“队列存数据”这个层面(毕竟它根本不存),而在于线程等待结构的内存布局与缓存友好性:
-
TransferStack的 LIFO 操作天然局部性好,栈顶节点大概率还在 CPU 缓存行里;TransferQueue的 FIFO 需要维护头尾指针+链表跳转,容易引发 false sharing - 公平模式下,多个线程争抢同一个锁队列头,CAS 冲突概率上升;非公平模式中,“撞上就拿走”的策略大幅降低锁竞争密度
- 真正影响业务吞吐的,往往是生产者与消费者处理耗时的方差——如果两者节奏差异大,非公平模式更容易放大饥饿,此时吞吐数字虽高,但尾部延迟(P99)可能陡增
所以不要只看平均吞吐,压测时务必观察 take() 和 put() 的阻塞时间分布,尤其是 timed 版本(如 poll(1, TimeUnit.SECONDS))是否频繁超时。










