
偏向锁在什么情况下会失效并撤销
偏向锁不是永久有效的,一旦有其他线程尝试获取同一把锁,JVM 就必须撤销它。这个过程叫 revoke_bias,开销不小——它需要安全点(safepoint),暂停所有线程,遍历栈帧检查是否持有该锁。
常见触发场景:
- 另一个线程调用
monitorenter时发现锁对象已偏向但不是自己,触发撤销 - 锁对象被调用了
wait()、notify()或hashCode()(因为 hash 值会写入对象头,破坏偏向结构) - JVM 启动时未开启偏向锁(
-XX:-UseBiasedLocking),或运行中通过BiasedLockingStartupDelay延迟启用后又被竞争打断
注意:撤销后对象头恢复为“无锁”状态,下次争用直接走轻量级锁路径,不会退回偏向。
轻量级锁自旋失败后怎么升级到重量级锁
轻量级锁靠 CAS 替换对象头中的指针实现,失败说明已有线程在持有锁。此时会进入自旋等待——但自旋不是无限的,由 PreBlockSpin(默认 10 次)和 CPU 核心数共同限制。
立即学习“Java免费学习笔记(深入)”;
升级条件很明确:
- 自旋期间锁被释放,当前线程抢到,继续轻量级锁流程
- 自旋耗尽仍没抢到,且锁持有线程还在运行(非阻塞态),则升级为重量级锁:将锁对象标记为
marked for inflation,新建ObjectMonitor,原线程挂起进_WaitSet或_EntryList - 如果持有线程已退出同步块,对象头可能已恢复,此时无需升级,重试 CAS 即可
关键点:inflation 只发生一次。之后所有争用都直接走重量级锁逻辑,不再尝试轻量或偏向。
synchronized 在逃逸分析后可能完全消除
逃逸分析(Escape Analysis)是 JIT 的优化手段,判断一个对象是否“逃逸”出当前方法或线程。如果锁对象是局部变量、未被返回、未被传入其它方法,JVM 可能判定它不会被多线程共享。
这时会发生锁消除(Lock Elision):
- 不生成任何锁操作字节码,
monitorenter/monitorexit被直接删掉 - 前提是开启了
-XX:+EliminateLocks(默认开启,依赖-XX:+DoEscapeAnalysis) - 常见于
StringBuffer.append()、Vector.get()等内部加锁方法,在局部使用时被优化掉
验证方式:用 -XX:+PrintCompilation 和 -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly 查看编译后代码,或用 JMH 测性能差异——消除后就是纯对象操作,零同步开销。
重量级锁的 park/unpark 怎么和 ObjectMonitor 关联
重量级锁的核心是 ObjectMonitor 结构体,每个锁对象(java.lang.Object 实例)在首次膨胀时关联一个。它包含 _WaitSet(调用 wait() 的线程队列)、_EntryList(等待获取锁的线程队列)和 _owner(当前持有者)。
线程阻塞实际调用的是 Unsafe.park(false, 0),但背后由 ObjectMonitor::EnterI 和 ObjectMonitor::Wait 调度:
- 争用失败时,线程被加入
_EntryList,然后park()挂起 -
notify()从_WaitSet摘一个线程,移到_EntryList尾部,再unpark() -
notifyAll()则批量转移 + 批量unpark()
注意:park()/unpark() 是 JVM 对 OS 线程调度的封装,底层可能是 futex(Linux)或 WaitForSingleObject(Windows),和 Java 层的 Thread.sleep() 无关。
真正容易被忽略的是:锁升级不可逆,且偏向锁撤销、轻量锁自旋、重量级锁阻塞这三步的开销差异极大——从纳秒级到微秒级再到毫秒级。别只盯着 synchronized 关键字,得看对象生命周期、线程竞争模式和 JVM 参数组合是否匹配。










