偏向锁竞争测试需验证第二个线程几乎同时争用已偏向对象时是否触发撤销与升级;须启用偏向锁、确保对象已偏向、避开安全点干扰,并通过日志、性能变化或Mark Word检查确认撤销。

偏向锁的竞争测试,核心是验证“当第二个线程尝试获取已被偏向的锁时,是否触发撤销与升级”。这不是靠代码打印日志就能直接观察的过程,而是需结合 JVM 参数、对象状态和线程行为综合判断。
偏向锁竞争触发的关键条件
偏向锁只有在真正发生线程竞争时才会被撤销并升级。注意:单纯启动多个线程不等于触发竞争——必须让它们**几乎同时**尝试进入同一个同步块,且该锁对象已处于偏向状态(即被第一个线程成功偏向过)。
- 对象必须已完成初始化,并被至少一个线程执行过 synchronized 块(完成偏向锁建立)
- 第二个线程需在偏向锁未被撤销前,调用同一对象的 synchronized 方法或代码块
- JVM 需启用偏向锁(JDK 15+ 默认关闭,需显式加 -XX:+UseBiasedLocking)
- 测试需避开安全点延迟——偏向锁撤销发生在全局安全点,因此要避免长时间 GC 或长耗时操作干扰时机
可验证的竞争现象
虽然无法直接读取“偏向锁已撤销”这样的布尔值,但可通过以下可观测行为确认竞争发生:
- 日志输出:添加 -XX:+PrintBiasedLockingStatistics -XX:+TraceBiasedLocking,JVM 会打印偏向撤销次数、失败原因(如“revoked due to contention”)
- 性能变化:在稳定偏向状态下,单线程反复加锁耗时趋近于零;一旦引入第二竞争线程,首次竞争会导致明显延迟(偏向撤销 + 轻量级锁建立),后续则表现为轻量级锁的 CAS 开销
- 对象头检查:通过 Unsafe.objectFieldOffset 或 JOL(Java Object Layout)工具查看 Mark Word 变化——从存储线程 ID(偏向锁)变为指向栈中 Lock Record 的指针(轻量级锁)
- 线程栈行为:使用 jstack 观察,若出现 “waiting to lock” 且锁状态已非偏向,则说明已升级并进入阻塞/自旋路径
典型测试代码结构
以下片段用于构造可控竞争场景(注意控制线程启动时序):
立即学习“Java免费学习笔记(深入)”;
- 主线程先对目标对象执行一次 synchronized 块,确保其进入偏向状态
- 启动两个子线程,用 CountDownLatch 同步起始点,使它们几乎同时尝试进入同一同步块
- 禁用偏向锁批量撤销优化(加 -XX:BiasedLockingStartupDelay=0),避免初始延迟掩盖现象
- 运行时加上 -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly(高级)可进一步观察底层指令变化,但需 hsdis 支持
常见误判与注意事项
很多“看似竞争”的情况其实并未真正触发偏向锁撤销:
- 多个线程分别操作不同对象实例 → 每个对象独立偏向,无跨线程竞争
- 第二个线程在第一个线程释放锁后很久才进入 → 偏向锁可能已超时自动撤销(默认 4s),属于“老化”而非“竞争”
- 使用了 final 字段或锁对象被 JIT 优化为锁消除 → 根本没生成 Monitor,谈不上升级
- JDK 版本 ≥15 且未开启偏向锁 → 所有对象默认走轻量级锁路径,测试结果与预期不符


















