Synchronized底层实现深度解析
一、核心数据结构与机制
1.1 对象头结构
每个Java对象头包含Mark Word(64bit)和Klass Pointer(32bit/64bit),其中Mark Word存储锁状态信息:
| 64bit Mark Word | 32bit Klass Pointer |
|-----------------|---------------------|
| 锁状态(25bit) | 分代年龄(4bit) |
| 哈希码(31bit) | 锁指针(64bit) |
- 锁标志位:2bit标识锁状态(01未锁定/00轻量级锁/10重量级锁/11 GC标记)
- 分代年龄:记录对象经历的GC次数,超过阈值进入老年代
1.2 Monitor机制
每个对象关联一个Monitor(监视器锁),其核心结构包含:
- EntryList:等待获取锁的线程队列
- WaitSet:调用wait()进入等待的线程集合
- Owner:当前持有锁的线程指针
当线程执行monitorenter指令时:
- 检查Mark Word锁标志位
- 无竞争则CAS设置Owner为当前线程
- 存在竞争则加入EntryList阻塞等待
二、锁状态升级机制
2.1 锁升级流程
graph TD
A[无锁] -->|首次访问| B[偏向锁]
B -->|竞争出现| C[轻量级锁]
C -->|自旋失败| D[重量级锁]
2.2 偏向锁(Biased Locking)
- 实现原理:在Mark Word中记录线程ID(23bit)和Epoch(2bit)
- 适用场景:单线程重复访问同步代码块
- 性能优势:加锁仅需1次CAS操作(约20ns)
- 撤销条件:
- 其他线程竞争锁
- 调用
hashCode()方法 - 超过20次撤销后批量重偏向
2.3 轻量级锁(Lightweight Locking)
- 实现原理:通过CAS将对象头替换为指向线程栈中Lock Record的指针
- 自旋策略:
- 初始自旋10次(JDK1.6)
- JDK1.8改为自适应自旋(根据历史成功率动态调整)
- 失败条件:CAS超过自旋次数或检测到竞争
2.4 重量级锁(Heavyweight Locking)
- 实现机制:依赖操作系统Mutex实现线程阻塞/唤醒
- 性能损耗:用户态→内核态切换(约10μs)
- 触发条件:多线程持续竞争锁
三、关键优化技术
3.1 锁消除(Lock Elision)
通过逃逸分析判断锁对象是否被其他线程访问,若无竞争则消除同步:
// 示例:局部变量同步会被消除
public void method() {
Object lock = new Object();
synchronized(lock) { // 编译器优化后无同步操作
// 仅本线程访问的代码
}
}
3.2 锁粗化(Lock Coarsening)
合并相邻同步块减少锁获取次数:
// 优化前
for(int i=0; i<100; i++) {
synchronized(lock) { doA(); }
}
// 优化后
synchronized(lock) {
for(int i=0; i<100; i++) doA();
}
3.3 自适应自旋(Adaptive Spinning)
动态调整自旋次数:
- 成功获取锁则增加自旋次数(最大100次)
- 失败则减少自旋次数(最小0次)
四、底层指令与字节码
4.1 指令分析
- monitorenter:尝试获取锁,计数器+1
- monitorexit:释放锁,计数器-1
- 异常处理:同步代码块异常时自动调用monitorexit
4.2 字节码示例
public synchronized void method() {
// 业务逻辑
}
反编译后可见:
ACC_SYNCHRONIZED
五、性能调优参数
| 参数 | 作用 | 默认值 |
|---|---|---|
| -XX:+UseBiasedLocking | 启用偏向锁 | true |
| -XX:BiasedLockingStartupDelay=5000 | 延迟偏向锁生效时间 | 4000 |
| -XX:-UseSpinning | 禁用自旋锁 | false |
| -XX:PreBlockSpin=10 | 设置初始自旋次数 | 10 |
六、性能对比数据
| 锁状态 | 平均获取时间 | 适用场景 |
|---|---|---|
| 偏向锁 | 20ns | 单线程重复访问 |
| 轻量级锁 | 50ns | 多线程交替访问 |
| 重量级锁 | 10μs | 真实多线程竞争 |
七、演进历程
- JDK1.6前:纯重量级锁,性能瓶颈明显
- JDK1.6:引入锁分级机制(偏向→轻量→重量)
- JDK1.8:优化自旋策略,支持分段锁
- JDK15+:偏向锁默认禁用,优化重量级锁
通过深入理解这些底层机制,我们可以:
- 合理选择锁策略(如避免在高频竞争场景使用偏向锁)
- 通过JVM参数优化锁性能
- 利用锁消除/粗化等编译器优化提升效率