synchronized锁升级详解
synchronized是Java中实现线程同步的关键字,它在JVM内部实现了锁的升级机制,从偏向锁到轻量级锁再到重量级锁,这种优化是为了减少锁操作带来的性能开销。
1. 锁的四种状态
Java对象头中的Mark Word会记录锁的状态,主要有以下几种:
- 无锁状态:对象未被任何线程锁定
- 偏向锁状态:偏向于第一个访问锁的线程
- 轻量级锁状态:多个线程交替执行同步块
- 重量级锁状态:多个线程竞争同一把锁
2. 锁升级流程
2.1 偏向锁阶段
适用场景:只有一个线程访问同步块
工作原理:
- 当第一个线程访问同步块时,会在对象头和栈帧中的锁记录里存储偏向的线程ID
- 后续该线程进入和退出同步块时不需要进行CAS操作来加锁和解锁
- 只需要检查对象头的Mark Word是否存储了当前线程的偏向锁
优点:加锁和解锁不需要额外消耗,和非同步方法性能相差无几
升级时机:当另一个线程尝试获取锁时,偏向锁就会升级为轻量级锁
2.2 轻量级锁阶段
适用场景:多个线程交替执行同步块,没有真正竞争
加锁过程:
- JVM在当前线程的栈帧中创建锁记录(Lock Record)空间
- 将对象头中的Mark Word复制到锁记录中(Displaced Mark Word)
- 尝试用CAS将对象头中的Mark Word替换为指向锁记录的指针
- 如果成功,当前线程获得锁;如果失败,表示其他线程竞争锁,当前线程尝试使用自旋来获取锁
解锁过程:
- 使用CAS操作将Displaced Mark Word替换回对象头
- 如果成功,表示没有竞争发生;如果失败,表示存在竞争,锁会膨胀为重量级锁
优点:竞争的线程不会阻塞,提高了程序响应速度
升级时机:当自旋超过一定次数(默认10次,可用-XX:PreBlockSpin调整)或者有第三个线程尝试获取锁时,会升级为重量级锁
2.3 重量级锁阶段
适用场景:多个线程同时竞争同一把锁
工作原理:
- 依赖于操作系统底层的互斥量(mutex)实现
- 未获取到锁的线程会被阻塞,进入等待队列
- 需要从用户态切换到内核态,开销较大
特点:
- 线程阻塞和唤醒需要操作系统介入,消耗较大
- 适用于高并发场景,避免CPU空转
3. 锁升级图示
无锁状态 → 偏向锁 → 轻量级锁 → 重量级锁
4. 锁降级
HotSpot JVM中锁只能升级不能降级,但有一种特殊情况:在GC发生时,会暂停所有Java线程(STW),此时会撤销所有偏向锁。
5. 相关JVM参数
-XX:+UseBiasedLocking
:启用偏向锁(JDK15后默认禁用)-XX:BiasedLockingStartupDelay=0
:设置偏向锁启动延迟(默认4秒)-XX:+PrintFlagsFinal
:查看JVM所有参数-XX:+PrintSynchronizationStatistics
:打印同步统计信息
6. 实际应用建议
- 对于低竞争场景,锁升级机制能显著提升性能
- 高竞争场景下,可以考虑使用
java.util.concurrent
包中的显式锁 - JDK15后默认禁用偏向锁,因为现代应用往往更适合轻量级锁或重量级锁
锁升级机制是JVM对synchronized性能优化的重要手段,理解这一机制有助于编写更高效的多线程程序。