synchronized原理与锁升级
一个让P6和P7拉开差距的问题
面试官问:"synchronized是如何实现的?"
候选人小张回答:"synchronized通过monitorenter和monitorexit指令实现,底层依赖操作系统mutex。"
面试官点点头,继续追问:"那JDK 6对synchronized做了哪些优化?锁升级的过程是什么?"
小张愣了一下:"好像有偏向锁、轻量级锁..."
面试官:"具体说说升级过程?"
小张支支吾吾,没能说清楚。
这个问题是P6和P7拉开差距的关键。synchronized是Java中最基础的并发控制机制,但很多人只知道它"加锁",不知道它怎么加锁、怎么升级、为什么这么设计。
今天这篇文章,把synchronized的锁升级机制彻底讲透。
synchronized的基本使用
三种用法
编译后的字节码
编译后的字节码:
关键点:
ACC_SYNCHRONIZED标志:方法级别的同步monitorenter/monitorexit:代码块级别的同步
对象的内存布局
对象的组成
在HotSpot JVM中,对象由三部分组成:
- Mark Word:存储对象的哈希码、GC年龄、锁状态等信息
- Class Pointer:指向类元数据的指针,开启压缩后为4字节
- Instance Data:对象的实例字段
- Padding:对齐填充,保证对象大小是8字节的倍数
Mark Word的结构(64位)
锁升级的过程
锁状态转换图
第一阶段:无锁 → 偏向锁
触发条件:第一个线程访问synchronized代码块
升级过程:
- 线程在对象头的Mark Word中写入自己的线程ID
- 以后该线程进入同步块时,不需要任何CAS操作
- 直接获取锁
偏向锁的优点:消除再进入同步块时的CAS操作
偏向锁的延迟:
- JVM启动后4秒才开启偏向锁(
-XX:BiasedLockingStartupDelay=4000) - 原因:JVM启动时有大量线程创建又销毁,不需要偏向锁
撤销偏向锁的场景:
- 其他线程尝试获取锁
- 调用对象的hashCode()(需要存储哈希码,偏向锁无法存储)
- 调用wait/notify(重量级锁特性)
第二阶段:偏向锁 → 轻量级锁
触发条件:有其他线程尝试获取偏向锁,且发生竞争
升级过程:
- 原持有偏向锁的线程到达安全点,停止运行
- 遍历线程栈,查找偏向锁的锁记录(BiasedLockingRecord)
- 如果还在同步块中,升级为轻量级锁
- 其他线程开始自旋等待
第三阶段:轻量级锁 → 重量级锁
触发条件:自旋次数超过阈值,或自旋的线程超过CPU核心数
升级过程:
- 自旋失败后,锁膨胀为重量级锁
- Mark Word更新为指向Monitor的指针
- 未获取锁的线程进入Monitor的等待队列
- 线程切换到内核态,被阻塞
重量级锁的开销:
- 用户态 → 内核态切换(context switch)
- 线程阻塞和唤醒
- 约几百纳秒到几微秒的延迟
锁升级的触发条件总结
轻量级锁的原理
锁记录(Lock Record)
轻量级锁在线程栈帧中创建锁记录:
加锁过程
CAS操作:
解锁过程
重量级锁的原理
Monitor(ObjectMonitor)的结构
加锁过程
自旋优化
生产中的注意事项
减少锁粒度
锁消除
JIT编译器会自动消除不必要的锁:
锁粗化
避免热点数据竞争
JDK 15之后的变化
废弃偏向锁
原因:
- 现代应用很少真正从偏向锁受益
- 偏向锁的维护成本高
- 简化JVM实现
轻量级锁优化
面试中的高频追问
追问1:synchronized和ReentrantLock的区别?
追问2:为什么synchronized在JDK 6之前性能差?
因为JDK 6之前只有重量级锁,每次加锁都要进入内核态(系统调用),即使没有实际竞争也要经历用户态→内核态的切换。JDK 6引入偏向锁和轻量级锁后,优化了无竞争和低竞争场景。
追问3:锁能降级吗?
不能。HotSpot JVM只实现了锁升级,没有实现锁降级。这是因为:
- 实现锁降级需要更多复杂度
- 大部分场景锁升级后不会再降级
- 简化实现,提高性能
追问4:synchronized的wait/notify为什么要在同步块中调用?
因为wait/notify需要获取对象的monitor,而synchronized是获取monitor的唯一方式。在同步块外调用会抛IllegalMonitorStateException。
【学习小结】
- synchronized实现:monitorenter/monitorexit字节码指令
- 对象内存布局:Mark Word + Class Pointer + Instance Data
- 锁升级流程:无锁 → 偏向锁 → 轻量级锁 → 重量级锁
- 偏向锁:消除无竞争时的CAS操作,第一个线程ID存储在Mark Word
- 轻量级锁:CAS将Mark Word复制到栈,指向Lock Record
- 重量级锁:Monitor机制,涉及用户态→内核态切换
- 生产建议:减少锁粒度、避免热点竞争、善用并发工具
延伸阅读: