偏向锁在其他线程竞争时被撤销,需全局安全点暂停所有线程,回退到无锁状态再重新竞争;轻量级锁通过有限CAS自旋,失败后达阈值才膨胀为重量级锁。

偏向锁怎么被撤销?别等GC来帮你
偏向锁不是永久贴在对象头上的,一旦有其他线程尝试获取同一把锁,JVM 就得撤销它。这个过程开销不小,而且是全局安全点操作——意味着所有应用线程都得暂停。
- 常见错误现象:
BiasedLockingRevocation日志频繁出现,或VM operation (safepoint)延迟明显升高 - 触发条件:第二个线程调用
monitorenter(比如 synchronized 块进入),且该对象已偏向某线程但当前不持有锁 - 撤销不是“升级”,而是回退到无锁状态,再重新竞争;如果紧接着要进轻量级锁,就得走一次 CAS 尝试
- 参数控制:
-XX:-UseBiasedLocking可直接禁用(JDK 15+ 默认关闭,但低版本仍默认开启)
轻量级锁的 CAS 自旋到底转几次?
轻量级锁靠 CAS 替换对象头中的 Mark Word 实现,失败就自旋重试。自旋不是无限的,次数由 JVM 决定,但可干预。
- 常见错误现象:高竞争下 CPU 占用飙升,但锁还没膨胀成重量级,
os::is_MP()为 true 时自旋逻辑才生效 - 自旋次数默认值取决于是否启用
-XX:+UseSpinning(JDK 6–8 默认开启,JDK 9+ 默认关闭) - 可用参数微调:
-XX:PreBlockSpin=10(仅 JDK 6–8 有效),但现代 JVM 更倾向用自适应策略,比如根据前一次自旋是否成功动态调整 - 注意:自旋消耗的是当前线程的 CPU 时间,对单核机器几乎无意义,反而加剧上下文切换
什么时候会真正膨胀成重量级锁?
轻量级锁自旋失败后,并不立刻膨胀。JVM 会先检查是否达到阈值(如自旋次数超限、或等待队列已有线程),再调用 ObjectSynchronizer::inflate 把锁升级为重量级。
- 关键判断点:对象头中 Mark Word 的锁标志位从
00(轻量级)变成10(重量级),同时指向一个ObjectMonitor结构 - 膨胀后,所有后续竞争者直接挂起进
_WaitSet或_EntryList,不再自旋 - 性能影响显著:一次膨胀涉及内存分配(
ObjectMonitor)、系统调用(pthread_mutex_lock)、线程状态切换;比轻量级锁慢 10–100 倍 - 验证方式:用
jstack看线程状态是BLOCKED而非RUNNABLE,或通过-XX:+PrintGCDetails -XX:+PrintSafepointStatistics观察锁相关 safepoint 活动
偏向锁和轻量级锁共存吗?
不能。一个对象在同一时刻只能处于一种锁状态,状态转换是单向的:无锁 → 偏向锁 → 轻量级锁 → 重量级锁。但“偏向”被撤销后,对象回到无锁状态,下次竞争可能直接走轻量级锁路径。
立即学习“Java免费学习笔记(深入)”;
- 容易踩的坑:误以为“开启了偏向锁,所有锁都会先偏向”,其实只有首次进入 synchronized 且满足条件(未禁用、未批量撤销、线程未退出等)才会尝试设置偏向
- 批量撤销机制存在:当某个类的偏向锁被撤销超过 20 次(
-XX:BiasedLockingBulkRevokeThreshold=20),该类所有新对象直接跳过偏向,新建即无锁 - 批量重偏向也存在:若某类对象大量被同一线程重复加锁,JVM 可能触发
BiasedLockingBulkRebiasThreshold机制,避免反复撤销/重置 - 真实场景中,Web 应用里短生命周期对象 + 多线程争抢,往往根本走不到偏向阶段,直接轻量级锁起步,甚至快速膨胀
锁升级不是自动优化,而是权衡结果;每一步都带着代价,而最常被忽略的是:偏向锁撤销和轻量级锁自旋的临界点,其实高度依赖具体 workload 和 GC 配置,离了压测数据,光看理论路径容易误判。










