Java synchronized 实现原理与锁升级机制详解

65 阅读4分钟

一、概述

synchronized是 Java 中实现线程同步的关键字,它提供了一种内置的锁机制,可以确保多个线程在访问共享资源时的互斥性。随着 JDK 版本的迭代,synchronized的性能得到了显著优化,其中最重要的优化就是锁升级机制。

二、底层实现基础

2.1 Java 对象头

在 JVM 中,每个 Java 对象在内存中都包含三部分:

  • 对象头(Header)
  • 实例数据(Instance Data)
  • 对齐填充(Padding)

synchronized的锁信息主要存储在对象头Mark Word中。

64位 JVM 下 Mark Word 结构:

锁状态25 bit31 bit1 bit4 bit1 bit (偏向锁位)2 bit (锁标志位)
无锁unused对象的 hashCode分代年龄分代年龄001
偏向锁线程ID(54bit) Epoch对象分代年龄1分代年龄101
轻量级锁指向栈中锁记录的指针 (62 bit)00
重量级锁指向互斥量(Monitor)的指针 (62 bit)10
GC标记与GC相关的信息(62 bit)11

注意:自 JDK 15 起,偏向锁被默认禁用,但理解其原理仍有重要意义。

2.2 监视器(Monitor)

Monitor 是实现同步的底层对象,每个 Java 对象都可以关联一个 Monitor:

  • synchronized代码块 → monitorentermonitorexit指令
  • synchronized方法 → ACC_SYNCHRONIZED方法访问标志

三、锁升级过程

锁升级路径:无锁 → 偏向锁 → 轻量级锁 → 重量级锁

3.1 偏向锁(Biased Locking)

设计初衷:减少同一线程重复获取锁的开销。 工作流程

  1. 第一个获取锁的线程将线程ID CAS 到对象头
  2. 该线程再次进入时直接检查线程ID,匹配则直接获取锁
  3. 其他线程竞争时,开始偏向锁撤销

优缺点

  • ✅ 无竞争时性能极佳
  • ❌ 竞争激烈时撤销成本高

3.2 轻量级锁(Lightweight Locking)

设计初衷:当存在轻度锁竞争时,通过自旋避免线程阻塞。 加锁过程

// 伪代码流程
1. 在栈帧中创建锁记录(Lock Record)
2. 拷贝对象头Mark Word到锁记录(Displaced Mark Word)
3. CAS将对象头指向锁记录指针
4. 成功则获得锁,失败则自旋重试

自适应自旋:JDK 6 引入,根据上次自旋结果动态调整自旋次数。

3.3 重量级锁(Heavyweight Locking)

触发条件:轻量级锁自旋失败后升级。 特点

  • 基于操作系统互斥量(mutex)实现
  • 线程阻塞和唤醒需要内核态切换
  • 开销最大但可保证公平性

四、锁升级流程图

flowchart TD
    A[线程进入同步块] --> B{检查锁状态}
    
    B -->|无锁(01)| C{偏向锁是否启用?}
    C -->|是| D[CAS设置线程ID]
    D -->|成功| E[获得偏向锁]
    D -->|失败| F[偏向锁撤销]
    
    C -->|否| F
    
    B -->|偏向锁(01)| G{线程ID是否匹配?}
    G -->|是| E
    G -->|否| F
    
    F --> H[升级为轻量级锁]
    H --> I[创建锁记录拷贝Mark Word]
    I --> J[CAS替换对象头]
    J -->|成功| K[获得轻量级锁]
    J -->|失败| L[自适应自旋]
    L --> M{自旋成功?}
    M -->|是| K
    M -->|否| N[升级为重量级锁]
    
    B -->|轻量级锁(00)| O[检查锁记录指针]
    O -->|指向当前线程| K
    O -->|重入| P[添加空锁记录]
    
    B -->|重量级锁(10)| Q[进入等待队列阻塞]
    
    E --> R[执行同步代码]
    K --> R
    P --> R
    Q --> R
    
    R --> S[退出同步块]
    S --> T{当前锁状态}
    
    T -->|偏向锁| U[什么都不做]
    T -->|轻量级锁| V[CAS恢复Mark Word]
    V -->|成功| W[解锁完成]
    V -->|失败| X[已升级为重量级锁<br>唤醒等待线程]
    T -->|重量级锁| Y[释放锁并唤醒线程]

五、各锁状态对比

锁状态优点缺点适用场景
偏向锁无竞争时开销极小竞争时撤销成本高单线程重复访问
轻量级锁线程不阻塞,响应快自旋消耗CPU低竞争、同步块小
重量级锁不消耗CPU,公平线程切换开销大高竞争、同步块大

六、实践建议

  1. 减少锁粒度:缩小同步代码块范围
  2. 避免锁竞争:使用并发容器、线程局部变量等
  3. 监控锁状态:借助JVM参数监控锁升级情况
  4. 考虑替代方案:在高并发场景下可考虑 ReentrantLock

七、总结

synchronized的锁升级机制体现了 JVM 在性能优化上的智慧:

  • 从低开销锁开始,按需升级
  • 在响应时间和吞吐量间寻求平衡
  • 适应不同竞争程度的并发场景

理解这一机制有助于我们编写更高效的并发程序,并在出现性能问题时能够准确诊断和优化。


本文基于 JDK 8+ 版本编写,具体实现细节可能因 JVM 厂商和版本有所不同。