当前位置:首页>java>Java synchronized 锁升级机制

Java synchronized 锁升级机制

  • 2026-10-11 08:04:07
Java synchronized 锁升级机制

Java 中 synchronized 关键字实现的锁是可升级的 biased lock,从无锁状态开始,根据竞争情况逐步升级:偏向锁(Biased Locking)→ 轻量级锁(Lightweight Locking)→ 重量级锁(Heavyweight Locking)。此设计旨在减少 uncontended 同步的性能开销,同时保证高竞争场景下的正确性。

Mark Word 结构

HotSpot 在对象头(Object Header)中存储锁状态。对象头分为两部分:

字段
32 位 JVM
64 位 JVM(未压缩 oops)
存储内容
32/64 位数据
64 位数据
用途
hash code、GC 分代年龄、锁状态、偏向线程 ID、锁记录指针、Monitor 指针等
同上

锁状态由对象头的最后 2 位标识:

状态
标志位(最后 2 位)
存储内容
无锁(Normal)
01
对象的 hash code、GC 分代年龄等
偏向锁(Biased)
01
偏向线程 ID + epoch
轻量级锁(Lightweight)
00
指向栈帧锁记录(Lock Record)的指针
重量级锁(Heavyweight)
10
指向 Monitor 对象的指针
GC 标记
11
用于标记(GC 期间)

64 位 JVM 默认开启 -XX:+UseCompressedOops(压缩普通对象指针),对象头实际占用 96 位(mark word 64 位 + type pointer 32 位)。

锁升级流程

1. 偏向锁(Biased Locking)阶段

触发条件:锁对象第一次被线程获取,且 JVM 开启偏向锁(默认开启,JDK 15+ 默认关闭,可通过 -XX:+UseBiasedLocking 开启)。

流程:

  1. 对象 Mark Word 为 01(无锁状态),计算对象的 hash code 并存储(调用 System.identityHashCode() 时也会触发)
  2. 线程 A 进入同步块时,CAS 将 Mark Word 的偏向线程 ID 字段设置为当前线程 ID
    • CAS 成功:获取偏向锁,Mark Word 仍为 01,但偏向线程 ID 指向线程 A
    • CAS 失败:表示已有其他线程持有偏向锁,触发偏向锁撤销
  3. 偏向锁撤销流程:
    • 检查持有偏向锁的线程是否存活
    • 若存活且该线程仍在同步块中,则升级为轻量级锁
    • 若已退出同步块,则撤销偏向锁,恢复为无锁状态,等待下次竞争时重新偏向

优化:偏向锁撤销时,可使用批量重偏向(bulk rebias) 和批量撤销(bulk revoke) 机制,减少 CAS 失败带来的开销。

2. 轻量级锁(Lightweight Locking)阶段

触发条件:

  • 偏向锁撤销
  • 多个线程交替执行同步块(非并发竞争)
  • 对象 hash code 已计算(偏向锁失效)

流程:

  1. 每个线程在栈帧中创建 Lock Record(锁记录,也叫 Displaced Mark Word)
    • Lock Record 结构:[lock record pointer | empty | ... ]
    • 将对象当前 Mark Word 复制到 Lock Record 的 displaced mark word 字段
  2. 线程使用 CAS 将对象 Mark Word 指向自己的 Lock Record(指针)
    • CAS 成功:获取轻量级锁,Mark Word 标志位变为 00
    • CAS 失败:表示有竞争,自旋(spin)等待
  3. 自旋优化:
    • 默认自旋次数:-XX:PreBlockSpin(JDK 6 默认 10 次,后续版本自适应调整)
    • 自旋期间若锁释放,则 CAS 成功获取锁
    • 若自旋超过阈值,升级为重量级锁

3. 重量级锁(Heavyweight Locking)阶段

触发条件:

  • 轻量级锁自旋失败(竞争线程 > 1)
  • 锁持有时间超过自旋阈值
  • 调用 wait() / notify() 等需要操作系统参与的 API

流程:

  1. JVM 在堆内存中创建 Monitor 对象(java.lang.Object 的 monitor 字段)
  2. 将对象 Mark Word 更新为指向 Monitor 的指针,标志位变为 10
  3. 竞争线程进入 Entry Set(入口等待队列),通过操作系统 mutex 阻塞
  4. 锁释放时,Monitor 从 Wait Set(等待队列)或 Entry Set 中唤醒一个线程

开销:涉及用户态→内核态切换,性能大幅下降(千年级别开销)。

升级条件总结

条件
偏向锁 → 轻量级锁
轻量级锁 → 重量级锁
竞争线程数
1(偏向锁持有者与另一线程交替)
≥1(并发竞争)
锁持有时间
短(偏向锁持有时间 < 偏向锁超时时间)
长(超过自旋阈值)
自旋结果
CAS 失败后直接升级
自旋超过阈值后升级
关键参数
-XX:BiasedLockingStartupDelay
-XX:BiasedLockingBulkRebiasThreshold-XX:BiasedLockingBulkRevokeThreshold
-XX:PreBlockSpin
-XX:UseSpinning

锁降级(Lock Downgrade)

降级场景:重量级锁持有期间,等待线程调用 wait() 进入 Wait Set,此时 Monitor 仍为重量级状态;当持有线程调用 notify() 并退出同步块后,Monitor 会降级为轻量级锁或偏向锁(若只有一个线程重新竞争)。

注意:重量级锁不会直接降级为偏向锁(需先撤销偏向),但会在特定条件下重新偏向。

关键参数调优

参数
默认值
作用
-XX:+UseBiasedLocking
JDK 6-14 开启,JDK 15+ 关闭
启用偏向锁
-XX:BiasedLockingStartupDelay
4000(4 秒)
JVM 启动后延迟开启偏向锁的时间(ms)
-XX:BiasedLockingBulkRebiasThreshold
20
一个类实例超过此阈值时触发批量重偏向
-XX:BiasedLockingBulkRevokeThreshold
40
超过此阈值时禁用偏向锁,全部升级为轻量级锁
-XX:UseSpinning
true
启用轻量级锁自旋
-XX:PreBlockSpin
自适应(JDK 6 为 10)
自旋最大次数(自适应策略可动态调整)
-XX:SlowSymantics
-
调试锁语义性能的工具

最佳实践

  1. 减小锁粒度:使用细粒度锁(如 ConcurrentHashMap 的分段锁)减少竞争
  2. 锁分离:读写锁(ReentrantReadWriteLock)、StampedLock 乐观读
  3. 避免锁嵌套:防止死锁和锁升级开销
  4. 使用无锁结构:AtomicInteger、LongAdder(高并发累加场景)
  5. 合理设置锁超时:tryLock(timeout) 避免无限等待
  6. 减少同步块范围:同步代码块尽可能简短
  7. 谨慎使用 synchronized 修饰方法:方法级锁粒度较粗,考虑改为块级锁

面试要点

  • 核心:理解偏向锁、轻量级锁、重量级锁三阶段升级的触发条件和性能特征
  • Mark Word:掌握对象头的锁标志位和存储内容
  • CAS 与自旋:轻量级锁通过 CAS + 自旋实现,降低线程阻塞开销
  • Monitor 机制:重量级锁依赖操作系统 mutex,涉及用户态/内核态切换
  • 适用场景:
    • 偏向锁:单线程反复执行同步块
    • 轻量级锁:多线程交替执行,无并发
    • 重量级锁:多线程并发竞争
  • 参数调优:能够解释常见 JVM 参数的作用和适用场景
  • 锁消除(Lock Elimination):JIT 编译时通过逃逸分析移除不必要的锁
  • 锁粗化(Lock Coarsening):JVM 将多个相邻同步块合并为一个

常见问题

Q:synchronized 为什么比 ReentrantLock 慢?A:ReentrantLock 基于 AQS 实现,支持非公平/公平锁、可中断、超时等高级特性;synchronized 是 JVM 内置锁,在 JDK 6 后已优化至与 ReentrantLock 性能相当,但在复杂场景下 ReentrantLock 更灵活。

Q:偏向锁在什么情况下会失效?A:

  • 调用 System.identityHashCode(obj) 计算 hash code
  • 锁撤销(另一线程竞争)
  • 调用 wait() / notify()(需升级为重量级锁)
  • 批量重偏向或批量撤销达到阈值

Q:轻量级锁自旋过多会有什么问题?A:自旋占用 CPU 资源,若长时间无法获取锁,会导致 CPU 利用率飙升,影响其他线程执行。

Q:如何查看锁状态?A:

  • jstack <pid>:查看线程栈,识别 BLOCKED / WAITING 状态
  • jstat -gc <pid>:监控 GC,间接判断锁竞争
  • -XX:+PrintSafepointStatistics:查看安全点停顿时间(包含锁升级开销)

性能对比(近似值)

锁类型
开销(相对值)
适用场景
无锁
1x
无竞争
偏向锁
1-3x
单线程、无竞争
轻量级锁
3-10x
交替执行、无并发
重量级锁
100-1000x
高并发竞争

数值仅为相对参考,实际性能受 JVM 版本、硬件、锁持有时间等多因素影响。

最新文章

随机文章