锁升级不可逆是HotSpot在安全、开销与实现复杂度间的明确取舍:重量级锁无法降级因需唤醒内核态线程并清空Monitor状态,开销大且风险高;偏向锁撤销后不重偏、轻量级锁失败不回退偏向,均因竞争已存在,重试成功率低且破坏单向升级路径的确定性。

锁升级不可逆,不是设计失误,而是 HotSpot 在安全、开销、实现复杂度三者间做的明确取舍。
为什么撤销重量级锁不回退到轻量级锁
重量级锁的 Monitor 对象一旦被创建并关联到对象头(Mark Word 中存的是指向 ObjectMonitor* 的指针),就意味着线程已进入操作系统内核态等待队列。此时若允许“降级”,需满足:
- 所有阻塞线程必须被唤醒并重新参与用户态自旋竞争 —— 这会引发大量上下文切换和调度开销
- Monitor 内部状态(如等待队列、EntryList、Owner 字段)必须安全清空或复用 —— 但 Monitor 是 C++ 堆上分配、带引用计数的重量级结构,回收/重置成本高
- JVM 无法可靠判断“当前是否真无竞争”:一个线程刚释放锁,另一个已在内核态排队,表面“空闲”实则隐含竞争
HotSpot 源码中 ObjectSynchronizer::inflate 只负责向上膨胀(inflate),没有对应的 deflate 实现。这不是遗漏,而是刻意省略 —— 降级带来的收益远低于其引入的逻辑分支、内存屏障和竞态风险。
偏向锁撤销后为何不回到无锁状态
偏向锁撤销(RevokeBias)确实会把对象头恢复为“无锁”格式(锁标志位 01),但这个“无锁”只是表象:
- 撤销操作必须在全局安全点(
SafePoint)执行,已触发一次 STW;若紧接着又有线程尝试获取锁,大概率直接走轻量级锁路径,跳过再次尝试偏向 - HotSpot 默认开启
-XX:+UseBiasedLocking,但一旦某个类的偏向锁被批量撤销(例如通过BiasedLockingBulkRevokeThreshold触发),该类后续新建对象将**直接禁用偏向**,从无锁跳转到轻量级锁 - 源码中
markOopDesc::set_biasable和BiasedLocking::revoke_at_safepoint都不提供“撤销后自动重偏”的机制 —— 因为重偏需再次 CAS,而此时已有竞争,成功率极低,纯属浪费
轻量级锁自旋失败后为何不重试偏向
轻量级锁的入口函数是 ObjectSynchronizer::slow_enter,它在 CAS 失败、自旋耗尽后,会直接调用 inflate 升级为重量级锁。这里不会回退检查“能否再偏向”,原因很实际:
- 自旋失败说明至少有两个活跃线程在争抢,已违反偏向锁“单线程长期持有”的前提假设
- 再次尝试偏向需修改
Mark Word并写入线程 ID,但此时对象头可能已被其他线程的轻量级锁记录污染(标志位为00),CAS 会失败 - HotSpot 源码中
fast_enter和slow_enter的控制流是单向的:无锁 → 偏向 → 轻量 → 重量,没有反向跳转逻辑
真正容易被忽略的是:锁升级路径的“不可逆”,本质是 JVM 放弃了对运行时竞争模式的动态预测。它不试图在每次锁操作时都重新评估“现在适不适合偏向”,而是用一次性的状态跃迁换取确定性行为和更少的分支判断 —— 这才是性能平衡点所在:宁可多升一级,也不愿多判一次。










