object.notify()唤醒顺序在hotspot中实际表现为fifo但不被java规范保证,因此不可依赖;应使用notifyall()配合条件检查或lock/condition显式队列实现可靠顺序。

Java 中 Object.notify() 的唤醒顺序**不是随机的,也不是严格保证的 FIFO**,而是由 JVM 实现决定的——在主流 HotSpot 虚拟机中,它**实际表现为 FIFO(先进先出)**,但 Java 语言规范**不承诺任何顺序**,因此不能依赖。
为什么说“看似 FIFO,但不可靠”
HotSpot 的 notify() 底层调用 ObjectMonitor::DequeueWaiter(),从 _WaitSet 链表头部取一个线程唤醒,这个链表正是按 wait 入队顺序构建的。所以大量实测(尤其在低竞争、单核或可控调度下)会看到唤醒顺序与等待顺序一致。
但问题在于:
– 线程被 wait 后进入 _WaitSet 的时机受锁释放、GC 暂停、OS 调度延迟等影响,waitList 记录的“入队顺序”未必等于 JVM 内部真实入队顺序;
– 多个线程几乎同时 wait 时,JVM 可能因并发插入导致链表顺序微调;
– 不同 JDK 版本、GC 模式(如 ZGC vs Serial)、甚至不同操作系统线程调度策略,都可能改变行为。
如何设计一个有效测试
要观察 notify 行为,关键不是“看一次结果”,而是**控制变量 + 多次运行 + 统计偏差**:
- 让每个线程在
synchronized块内调用wait()前,用System.nanoTime()或自增 ID 记录逻辑序号,并存入全局List<string></string>(如waitOrder) - 唤醒方每次
notify()后,被唤醒线程立即记录自己 ID 并加入notifyOrder - 每轮测试启动 N 个线程(建议 ≥20),全部 wait 完毕后再统一 notify(避免唤醒干扰等待过程)
- 重复运行 100 次,统计“唤醒顺序完全匹配等待顺序”的次数占比
- 对比不同参数:加
Thread.sleep(1)控制 wait 时间差、关闭 JIT、换 GC 参数(-XX:+UseSerialGC)、切换 JDK 版本
典型测试代码的关键点
下面这段逻辑比简单 for 循环更可靠:
- 用
CountDownLatch确保所有线程都已进入 wait 状态,再开始 notify - 唤醒线程不直接打印,而是写入线程安全容器(如
CopyOnWriteArrayList),避免输出干扰时序 - 每个线程 wait 前先获取锁、登记序号、再 wait;唤醒后立即记录并退出,防止二次竞争
- 禁止在 wait/notify 块中做耗时操作(如 IO、sleep),否则会扭曲调度表现
测试结果怎么看
如果 100 次运行中,95 次以上唤醒顺序 = 等待顺序 → 这是 HotSpot 的 FIFO 表现,但仍是实现细节;
如果出现几次乱序(尤其在高负载或启用了 G1/ZGC 时)→ 正常,说明它并非语义保证;
如果多数情况乱序(比如用某些非 HotSpot JVM 或老版本 OpenJDK)→ 说明该实现未采用 FIFO 策略。
真正重要的结论是:**无论测试结果多“整齐”,都不应把 notify 当作有序调度工具使用。业务需要顺序唤醒,必须用 notifyAll() + while 条件检查 或 Lock + Condition + 显式队列。**
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











