偏向锁在发生竞争(如其他线程执行monitorenter)、调用wait/notify/hashcode或jvm批量撤销时会被撤销,且必须在全局安全点进行。

偏向锁在什么条件下会撤销?
偏向锁不是永久生效的,只要发生竞争就会被撤销——哪怕只有一次其他线程尝试获取该锁。JVM 在检测到 monitorenter 指令被另一个线程执行时,就会触发偏向锁撤销流程。
- 撤销必须在全局安全点(safepoint)进行,意味着要等所有线程到达安全点才能操作,有暂停开销
- 如果对象已被多个线程交替加锁,JVM 可能直接禁用该类的偏向锁(通过
-XX:-UseBiasedLocking或运行时批量撤销) - 调用
Object.wait()、Object.notify()、hashCode()也会导致偏向锁失效——因为这些操作会要求对象头存储额外信息,与偏向锁结构冲突
轻量级锁是怎么“自旋”的?
轻量级锁本质是把对象头的 Mark Word 复制到线程栈帧的锁记录(Lock Record)中,再用 CAS 尝试替换对象头。失败即说明存在竞争,此时进入自旋逻辑。
- 自旋不是无限循环,默认最多 10 次(由
-XX:PreBlockSpin控制,JDK 6 后已废弃,实际由自适应策略决定) - 自旋期间线程不释放 CPU,适合临界区极短(纳秒~微秒级)、竞争不激烈的场景;若自旋失败,则升级为重量级锁
- 注意:自旋消耗的是当前线程所在 CPU 核心,高并发下可能造成不必要资源浪费,尤其在单核或容器 CPU 配额受限环境
重量级锁为什么一定涉及系统调用?
一旦升级为重量级锁,对象头的 Mark Word 就不再存锁状态,而是指向一个 ObjectMonitor 结构体指针。这个结构体内部维护了 _owner、_WaitSet、_EntryList 等字段,真正实现阻塞/唤醒语义。
- 线程挂起必须调用操作系统提供的原语,如 Linux 下的
futex_wait,这属于用户态到内核态的切换,开销远大于 CAS 或自旋 -
ObjectMonitor本身由 C++ 实现,在 JVM 堆外分配,生命周期与 Java 对象解耦;即使对象被 GC,monitor 可能仍存在(直到被复用或销毁) - 频繁升级到重量级锁往往意味着设计问题:比如锁粒度太大、同步块过长、或本可用无锁结构(如
AtomicInteger)却用了synchronized
如何验证锁是否真的升级了?
不能只看代码写法,得靠 JVM 自身输出或工具观测。最直接的方式是开启锁优化日志。
- 加 JVM 参数:
-XX:+PrintSynchronizationStatistics -XX:+UnlockDiagnosticVMOptions,但仅限 debug 版本 JDK - 更通用的是使用
jstack -l <pid></pid>查看线程堆栈中的锁状态,重量级锁会显示- parking to wait for,而轻量级锁不会出现这类阻塞标记 - 注意:JIT 编译后可能进行锁消除(Lock Elision)或锁粗化(Lock Coarsening),实际运行时甚至看不到锁操作——所以测试务必关闭 JIT(
-Xint)或确保代码已稳定运行足够长时间
锁升级过程不是线性可预测的自动流水线,它高度依赖运行时竞争模式、JVM 版本、甚至 GC 类型。同一个 synchronized 块,在不同压测阶段可能走完全不同的锁路径。观察它,永远要比假设它更可靠。








