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指令时:

  1. 检查Mark Word锁标志位
  2. 无竞争则CAS设置Owner为当前线程
  3. 存在竞争则加入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+:偏向锁默认禁用,优化重量级锁

通过深入理解这些底层机制,我们可以:

  1. 合理选择锁策略(如避免在高频竞争场景使用偏向锁)
  2. 通过JVM参数优化锁性能
  3. 利用锁消除/粗化等编译器优化提升效率