拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Java 并发灵魂拷问:notify/signal 唤醒顺序是规范还是实现?

Java 并发面试里一道看似简单的问题常能刷掉 90% 的人Object.notify()和Condition.signal()唤醒线程的顺序是什么随机还是 FIFO今天我们就从JVM 规范、HotSpot 源码、AQS 设计原理三个层面一次把这件事说透。结论Object.notify()synchronized配套JVM 规范没有规定顺序允许任意选择。JDK8 HotSpot 实现从WaitSet头部取线程表现为 FIFO但这只是“实现上的巧合”。工程红线严禁依赖该顺序因为notify唤醒后线程会进入EntryList重新竞争锁而synchronized是非公平锁最终执行顺序无法保证。Condition.signal()AQS /ReentrantLock配套严格按 FIFO 顺序唤醒唤醒的是等待队列头部线程。不是随机而且是设计约定业务可以合理依赖。一、synchronized wait() / notify()WaitSet 里的秘密synchronized的重量级锁底层依赖ObjectMonitor它内部有两个关键队列EntryList等待获取锁的线程队列竞争锁。WaitSet调用了wait()后释放锁并挂起的线程队列单向链表。notify()到底怎么选线程从JDK8 HotSpot 源码看notify()会从WaitSet中取出链表头部的第一个等待线程进行唤醒因此肉眼观测到的现象是先入先出FIFO。但是这恰恰就是 90% 的人掉坑的地方。误区 ①既然 HotSpot 是 FIFO业务代码就能依赖这个顺序绝对不能。原因有二这是 HotSpot 内部实现细节不是规范。Object.notify()的 Javadoc 写得很清楚The choice is arbitrary and occurs at the discretion of the implementation.任意 JVM 都有权实现成随机、优先级优先明天 HotSpot 改成随机也完全合法。唤醒顺序 FIFO ≠ 执行顺序 FIFO。notify唤醒的线程不会立刻拿到锁它会从WaitSet移入EntryList再次参与 monitor 竞争。而synchronized采用的是非公平锁策略新来的活跃线程完全可以“插队”先抢到锁。举个例子线程 A 先wait线程 B 后wait。notify确实按 FIFO 先唤醒了 A可就在 A 准备拿锁的瞬间外部线程 C 闯进来一把抢走了锁——A 继续阻塞。最终执行的顺序依然是“无序”的。误区 ②notify到底是不是“随机唤醒”这是全网最大的争议点。正确说法是规范层面允许随机我们应把它当作随机来设计程序。HotSpot 实现层面JDK8 是 FIFOJDK 高版本、带特定 JVM 参数、有中断/伪唤醒等场景可能打破这个现象。OpenJ9 等其它虚拟机完全可能是不同的策略。结论不要试图从notify的结果反推顺序写并发代码必须假设它是随机的。二、Lock Condition.await() / signal()精心设计的 FIFO如果你需要有顺序保证的线程唤醒应该用java.util.concurrent.locks下的Lock和Condition底层是 AQS。底层载体Condition Queue双向链表严格 FIFOCondition内部维护了一个独立的等待队列由firstWaiter和lastWaiter指针维护。await()线程释放锁然后被包装成节点插入等待队列尾部。signal()直接取出firstWaiter队首节点进行唤醒这是写在源码里的 FIFO。signalAll()从队首开始逐个唤醒队列中所有节点。signal()的唤醒顺序是设计目标不是实现巧合可以放心依赖。但依然要记住即使signal按 FIFO 精准唤醒被唤醒的线程仍然要回到 AQS 同步队列重新争锁。如果锁本身是非公平的默认ReentrantLock可设为公平/非公平最终的执行顺序仍可能受锁竞争影响但至少唤醒这个动作是 100% 有序的。三、一张图看懂区别API所属体系唤醒顺序能否依赖Object.notify()synchronizedJVM 规范无要求HotSpot 实现为 FIFO取WaitSet头部但唤醒后需竞争非公平锁最终乱序严禁依赖Condition.signal()AQS/Lock固定从 Condition 队列头部唤醒严格 FIFO可合理依赖四、细节1. 虚假唤醒与中断即使notify按 FIFO 唤醒wait()也可能被虚假唤醒或中断打断。这会使 WaitSet 内部的链表结构发生变化你肉眼看到的“有序”可能瞬间消失。2.notifyAll不是“一起跑”notifyAll会把所有线程从WaitSet移到EntryList但它们还是一个一个去抢锁执行顺序依然由锁竞争决定。3. 不要和signal的设计哲学搞混Object.notify()设计出来只是“随便叫醒一个”官方从没承诺过顺序。Condition.signal()设计出来就是为了在等待队列中精准按序激活这是Lock体系相较synchronized的一大优势。五、极简总结notify规范允许乱序HotSpot 碰巧写成有序属于实现彩蛋不能当饭吃。signal设计目标就是有序这是它存在的理由。理解了这一层下次面试官再问“notify是随机唤醒吗”你就知道他其实想听的是规范与实现的区别以及锁竞争对执行顺序的最终影响。把这篇文章的逻辑理清这个知识点你就彻底通关了。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门