synchronized 的非公平性源于锁释放后从无序的 entrylist 随机唤醒线程,且 wait/notify 唤醒的线程需重新进入 entrylist 竞争,jvm 未提供公平调度机制,仅保障正确性而非时序可预测性。

synchronized 的非公平性,根源不在 wait/notify 逻辑,而在于锁释放后对 EntryList 中线程的唤醒策略——JVM 不按等待先后顺序选取线程,而是随机挑选。
EntryList 是锁竞争的“无序排队区”
当多个线程同时尝试进入 synchronized 块但锁已被占用时,它们不会被排成一个 FIFO 队列,而是以单链表形式加入 EntryList。这个结构本身不维护插入时间戳,也不提供优先级排序能力。
- 线程进入 EntryList 后处于 BLOCKED 状态,仅表示“在等锁”,不意味着“在队首”
- 锁释放瞬间,JVM 从 EntryList 中选取一个线程唤醒,该选择由底层 C++ 实现决定,无时间顺序保证
- 新到达的线程(刚调用 synchronized)和已在 EntryList 中等待很久的线程,获得锁的概率理论上相同
WaitSet 唤醒后仍要回到 EntryList 重新竞争
调用 wait() 的线程会离开临界区、释放锁,并进入 WaitSet;被 notify() 唤醒后,它并不会直接拿到锁,而是先被移到 EntryList 尾部,和其他 BLOCKED 线程一样排队争锁。
- 即使某个线程在 WaitSet 中等了很久,被 notify 后也只是“获得竞争资格”,不是“获得锁”
- 它和刚到来的新线程、或已在 EntryList 中等待数毫秒的线程,在锁竞争中地位完全平等
- 这就造成一种叠加效应:两次非确定性——WaitSet 唤醒顺序不定 + EntryList 竞争胜出顺序不定
没有公平模式开关,底层不支持 FIFO 调度
与 ReentrantLock(true) 显式启用公平模式不同,synchronized 的 Monitor 实现(ObjectMonitor)在 JVM 源码中压根没提供公平调度路径。
- _EntryList 字段是普通指针链表,不带时间戳或计数器字段
- 唤醒逻辑集中在
ObjectMonitor::exit()中,调用的是平台相关的线程调度接口,而非队列 poll - 设计上放弃公平性是为了减少上下文切换开销和避免 convoy effect(即一个慢线程拖慢整条等待链)
公平性缺失不影响正确性,但影响可预测性
非公平不等于“错误”。只要满足互斥、可见、有序三大语义,程序逻辑依然安全。只是你无法假设“先到先得”。
- 若业务依赖严格时序(如任务按提交顺序执行),不应依赖 synchronized 的等待顺序
- 需要公平语义时,应改用
ReentrantLock(true)并配合Condition - 日常使用中,非公平反而提升吞吐:刚释放锁的线程可能还在 CPU 缓存中,立即重入比唤醒远处线程更快











