1. 锁升级机制的必要性背景
现代多核处理器环境下,线程同步的成本直接影响系统吞吐量。传统重量级锁(如操作系统互斥量)每次加锁/解锁都需要陷入内核态,上下文切换开销可达微秒级。而实际统计显示,超过98%的锁竞争持续时间短于1微秒,且多数情况下不存在真实竞争。
这种矛盾催生了自适应锁优化策略——根据实际竞争情况动态调整锁的实现方式。HotSpot虚拟机采用的分层锁升级机制包含四个阶段:无锁→偏向锁→轻量级锁→重量级锁。这种设计使得没有竞争时开销趋近于零,中等竞争时采用用户态自旋,只有高竞争时才触发系统调用。
2. 偏向锁原理与实现细节
2.1 偏向锁数据结构
对象头中的Mark Word在偏向模式下包含:
| 54 bits | 2 bits | 1 bit | 1 bit | 4 bits | | thread pointer| epoch | unused| lock | biased_lock flag|关键字段说明:
- thread pointer:记录首次获得锁的线程ID
- epoch:用于批量撤销的版本号
- biased_lock:偏向模式标志位(固定为1)
2.2 偏向锁获取流程
- 检查对象头中biased_lock标志是否为1
- 比较thread指针是否指向当前线程
- 匹配:直接执行临界区(仅需1次CAS验证)
- 不匹配:检查对象epoch是否有效
- 有效:尝试通过CAS竞争偏向权
- 无效:触发偏向锁撤销
关键优化点:当线程首次获取偏向锁时,只需在对象头写入线程指针,后续加锁仅需比较指针值,完全避免同步指令。
2.3 批量重偏向机制
当某个类的偏向锁撤销次数超过阈值(默认20次),JVM会触发批量重偏向:
- 递增该类的epoch版本号
- 遍历所有存活实例,将epoch更新为新值
- 新加锁请求会直接偏向新线程
实测表明该机制可使线程池场景下的锁性能提升3-5倍。
3. 轻量级锁的膨胀条件
3.1 触发场景
当出现以下情况时,偏向锁升级为轻量级锁:
- 多个线程交替访问同步块(非并发竞争)
- 偏向锁撤销次数超过阈值(默认40次)
- 调用wait()等特殊方法
3.2 栈帧锁记录结构
| 32/64 bits | | displaced mark word |轻量级锁通过栈帧中的Lock Record实现:
- 在栈帧分配Lock Record空间
- 将对象头的Mark Word复制到displaced字段
- 通过CAS将对象头指向Lock Record指针
3.3 自旋优化策略
JDK 6引入自适应自旋:
- 历史成功率>50%:延长自旋时间
- 历史成功率<30%:减少自旋次数
- 默认最大自旋次数:CPU核数>1时为10次
实测数据显示,合理设置自旋可使短临界区的吞吐量提升40%。
4. 重量级锁的底层实现
4.1 Monitor对象结构
ObjectMonitor { _header // 存储原始mark word _count // 重入计数 _waiters // 等待线程数 _recursions // 锁重入次数 _owner // 持有线程 _WaitSet // wait()线程队列 _EntryList // 阻塞线程队列 }4.2 内核态切换原理
重量级锁通过pthread_mutex_t实现,其阻塞过程:
- 通过futex系统调用进入等待
- 线程状态切换为TASK_UNINTERRUPTIBLE
- 触发调度器进行上下文切换(开销约5-20μs)
4.3 性能对比数据
| 锁类型 | 无竞争耗时 | 低竞争耗时 | 高竞争耗时 |
|---|---|---|---|
| 偏向锁 | 1-2ns | N/A | N/A |
| 轻量级锁 | 5-10ns | 50-100ns | 500ns-1μs |
| 重量级锁 | 20-30ns | 10-20μs | 100μs+ |
5. 锁降级机制的特殊处理
5.1 触发条件
当重量级锁满足以下条件时会尝试降级:
- 当前没有线程在EntryList等待
- 持有线程完成同步块执行
- 没有其他线程尝试获取锁
5.2 降级流程
- 将Monitor中的_header恢复到对象头
- 清除_owner指针
- 将锁状态标记为轻量级模式
- 保留_count等字段供快速重入
该机制使得长时间持有但低竞争的锁能回归轻量级状态,G1收集器中大量使用此优化。
6. 生产环境调优建议
6.1 参数配置参考
-XX:BiasedLockingStartupDelay=0 # 立即启用偏向锁 -XX:+UseHeavyMonitors # 禁用轻量级锁 -XX:PreBlockSpin=15 # 调整自旋次数6.2 诊断工具示例
- 查看锁竞争情况:
jstack pid | grep -A 10 "contended"- 分析偏向锁撤销:
-XX:+PrintBiasedLockingStatistics6.3 编码实践建议
- 避免在循环内创建新对象并加锁
- 同步块内不要调用耗时IO操作
- 对明确存在竞争的锁主动使用ReentrantLock
7. 常见问题排查实录
7.1 偏向锁失效场景
案例:线程池处理任务时出现性能下降
- 现象:TPS从5000骤降到800
- 分析:jstack显示大量RevokedBiasedLock
- 解决:增加-XX:BiasedLockingBulkRebiasThreshold=30
7.2 自旋浪费CPU
案例:CPU使用率100%但吞吐量低
- 现象:perf top显示大量CAS指令
- 分析:自适应自旋策略失效
- 解决:调整-XX:PreBlockSpin=5
7.3 重量级锁排队
案例:接口响应时间波动大
- 现象:jstack显示BLOCKED线程多
- 分析:同步块内有数据库查询
- 解决:拆解同步范围或改用读写锁