商城首页欢迎来到中国正版软件门户

您的位置: 首页 > 文章列表 > 编程开发 > synchronized 的非公平性:通过监控 Monitor 的 EntryList 与 WaitSet 分析为什么唤醒线程不保证公平性

synchronized 的非公平性:通过监控 Monitor 的 EntryList 与 WaitSet 分析为什么唤醒线程不保证公平性

  发布于2026-07-08 阅读(0)

扫一扫,手机访问

说说 synchronized 的非公平性这事儿。很多人容易把锅甩给 wait/notify,其实根子不在这——真正的关键,是锁释放之后,JVM 到底按什么规则从 EntryList 里挑线程去唤醒。结论先放在这里:它不按先来后到,而是随机选。换句话说,你排了半天队,可能还不如一个新来的“插队”快。

EntryList:一个没有“先来后到”概念的等候区

当一个线程试图进入 synchronized 块,但锁已经被别人占着,它不会去哪儿排个号,而是被塞进一个单向链表结构——也就是 EntryList。这个结构本身不记录线程进入的时间,也不具备任何排序能力。

几个关键点值得记住:

  • 线程进了 EntryList,状态变成 BLOCKED,但这只表示“我在等锁”,不代表“我排在最前面”
  • 锁释放的那一刻,JVM 从 EntryList 里随便挑一个线程唤醒。挑谁呢?由底层 C++ 实现说了算,跟等待时间长短没有任何关系
  • 新来报到的线程,和已经在 EntryList 里蹲了很久的线程,中签概率一模一样

说白了,这就像一个没人维持秩序的候车厅,门一开谁挤得快谁先上。

WaitSet 里的线程,醒了也得重新排队

调用 wait() 的线程会主动退出临界区、释放锁,然后一头扎进 WaitSet。等到被 notify()

  • 哪怕它在 WaitSet 里等了一万年,被叫醒后也只是获得了一次“参赛资格”,而不是直接拿锁
  • 它和刚来的新线程、已经排队等了几毫秒的线程,在锁竞争的牌桌上地位完全平等

这就造成了一个双重的非确定性:一方面,WaitSet 里哪个线程先被唤醒本身就不确定;另一方面,被唤醒后挤进 EntryList,能不能赢下锁又是另一回事。两重不确定性叠加,公平性基本无从谈起。

JVM 压根没给公平模式留后门

ReentrantLock(true) 显式开启的公平模式不同,synchronized 的底层 Monitor 实现——也就是 ObjectMonitor——在 JVM 源码里根本就没设计公平调度的路径。

  • _EntryList 字段就是一个普通指针链表,不带时间戳,也没有计数器字段来记录谁先谁后
  • 唤醒逻辑集中在 ObjectMonitor::exit() 里,调用的是平台相关的线程调度接口,而不是从队列里依次取元素
  • 设计上放弃公平性,核心意图是为了减少上下文切换的成本,同时避免所谓的“车队效应”——即一个慢线程拖慢整条等待链上的所有线程

所以这不是 bug,是设计选择,而且是一个很务实的选择。

公平性缺失不等于程序不安全

非公平不代表“错误”。只要程序满足了互斥、可见性和有序性这三个基本语义,逻辑上就是安全的。问题在于,你不能依赖线程的等待顺序来做任何时序上的假设。

  • 如果你的业务逻辑依赖于严格的任务执行顺序——比如按提交队列依次处理——那别指望 synchronized 能帮你做到
  • 需要公平语义的时候,直接用 ReentrantLock(true)Condition 组合拳
  • 日常开发中,非公平反而往往是性能更优的选择:刚释放锁的线程大概率还在 CPU 缓存里,让它立即重入,比唤醒另一个远端的线程要快得多

synchronized 的非公平性:通过监控 Monitor 的 EntryList 与 WaitSet 分析为什么唤醒线程不保证公平性

总之,synchronized 的非公平性不是个“缺陷”,而是 JVM 工程师出于性能考量做的一个清醒取舍。理解了这个机制,你就能在并发编程中更清楚地判断:什么时候该用它,什么时候该换工具。

本文转载于:https://www.php.cn/faq/2421230.html 如有侵犯,请联系zhengruancom@outlook.com删除。
免责声明:正软商城发布此文仅为传递信息,不代表正软商城认同其观点或证实其描述。

热门关注