Java 中 synchronized 的实现原理

Java 中 synchronized 的实现依托 JVM 的监视器锁(Monitor)机制对象头(Object Header)结构,并随 JDK 版本演进优化,引入锁升级策略(偏向锁、轻量级锁、重量级锁 )。以下从字节码基础、对象头、锁升级、Monitor 原理等维度详细拆解:

一、字节码层面的基础实现

synchronized 修饰 代码块方法 时,字节码实现有差异,但核心依赖 monitorenter/monitorexit 指令或隐式标记:

1. 同步代码块(显式指定锁对象)

编译后,代码块前后会插入 monitorenter(加锁)和 monitorexit(解锁)字节码指令:

  • monitorenter:线程执行此指令时,尝试获取锁对象的 Monitor 所有权。若成功,Monitor 计数器 +1;若失败,线程阻塞等待。
  • monitorexit:执行此指令时,Monitor 计数器 -1。当计数器归 0,锁被释放,唤醒等待线程。

示例(伪字节码):

synchronized (obj) { 
    // 业务逻辑
}
// 编译后字节码逻辑:
monitorenter  // 尝试获取 obj 的锁
// 业务逻辑字节码
monitorexit   // 释放 obj 的锁
2. 同步方法(隐式锁)
  • 实例方法:编译后方法的访问标志会增加 ACC_SYNCHRONIZED 标记,无需显式 monitorenter/monitorexit。JVM 调用方法时,自动检查该标记,尝试获取对象实例的 Monitor
  • 静态方法:同样用 ACC_SYNCHRONIZED 标记,但锁对象是类的 Class 对象(如 Xxx.class ),保证静态方法的线程安全。

二、对象头与 Mark Word 的关键作用

Java 对象在内存中布局分 对象头、实例数据、对齐填充,其中 对象头的 Mark Wordsynchronized 实现锁的核心载体:

1. Mark Word 存储结构

Mark Word 存储对象的哈希码、GC 分代年龄、锁状态标志持有锁的线程 ID 等信息。不同锁状态(无锁、偏向锁、轻量级锁、重量级锁 )下,Mark Word 内容动态变化:

锁状态Mark Word 存储内容(简化)锁标志位
无锁对象哈希码、分代年龄等01
偏向锁偏向线程 ID、分代年龄、偏向标志01(偏向)
轻量级锁指向线程栈中锁记录的指针00
重量级锁指向 Monitor 对象的指针10
2. 为什么任意对象可作为锁?

因每个对象都有 Mark Word,可存储锁状态、持有锁的线程 ID 等信息,天然支持作为锁的载体。这也是 synchronized (任意对象) 语法的底层依据。

三、锁升级策略(JDK 1.6+ 优化)

为解决早期 synchronized 直接使用“重量级锁”导致的性能问题,JDK 1.6 引入 偏向锁 → 轻量级锁 → 重量级锁 的升级流程,依据竞争程度动态调整:

1. 偏向锁(无竞争时优化)
  • 适用场景:单线程重复获取同一锁(无竞争)。
  • 加锁逻辑
    1. 线程第一次访问同步块时,JVM 标记对象头的 偏向锁标志(设为 1 ),并记录当前线程 ID 到 Mark Word。
    2. 后续该线程再次进入同步块,直接对比 Mark Word 中的线程 ID:若匹配,无需 CAS(比较并交换),直接进入代码块(类似“指纹识别” )。
  • 优点:消除无竞争场景下的 CAS 开销,提升单线程重复加锁效率。
  • 升级触发:若其他线程尝试竞争锁(检测到 Mark Word 中线程 ID 与当前线程不匹配 ),则撤销偏向锁,升级为轻量级锁
2. 轻量级锁(轻度竞争时优化)
  • 适用场景:多线程交替竞争锁(竞争不激烈,无线程阻塞 )。
  • 加锁逻辑
    1. 线程在栈帧中创建 锁记录(Lock Record),存储当前对象头的 Mark Word 副本。
    2. 通过 CAS 操作,尝试将对象头的 Mark Word 替换为“指向锁记录的指针”:
      • 成功:获取轻量级锁,进入代码块。
      • 失败:说明有竞争,JVM 自旋(循环重试 CAS )尝试获取锁;若自旋超过阈值(或竞争加剧 ),升级为重量级锁
  • 优点:用 CAS 替代操作系统级别的线程阻塞,减少上下文切换开销。
  • 解锁逻辑:通过 CAS 将对象头的 Mark Word 恢复为原始值(锁记录中的副本 ),若失败说明有竞争,需唤醒等待线程。
3. 重量级锁(激烈竞争时兜底)
  • 适用场景:多线程高并发竞争,存在线程阻塞等待。
  • 加锁逻辑
    1. JVM 为对象分配 Monitor 对象(操作系统级别的同步机制,非 Java 对象 ),并将对象头的 Mark Word 指向 Monitor 地址。
    2. 线程竞争锁时,若 Monitor 的 Owner 不为空(锁被占用 ),则进入 Entry List 阻塞等待。
  • Monitor 结构
    Monitor {
        Owner: 持有锁的线程(如线程 A 获得锁,Owner = A )
        Recursion Count: 重入次数(同一线程重入锁时,计数器 +1 )
        Entry List: 等待获取锁的线程队列(竞争失败的线程进入此处阻塞 )
        Wait Set: 调用 wait() 后等待唤醒的线程队列(与锁释放无关,是条件等待 )
    }
    
  • 解锁逻辑:线程释放锁时,Recursion Count 减 1;若归 0,Owner 置空,唤醒 Entry List 中等待的线程竞争锁。
  • 缺点:依赖操作系统内核线程调度,线程阻塞/唤醒涉及 上下文切换,性能开销大。

四、可重入性的实现

synchronized可重入锁,同一线程可多次获取同一对象的锁,依赖 Monitor 的 Recursion Count(重入计数器 )

  • 线程第一次获取锁时,Recursion Count = 1Owner 标记为当前线程。
  • 线程再次进入同步块(重入 ),Recursion Count += 1,无需阻塞。
  • 线程退出同步块时,Recursion Count -= 1;当 Recursion Count = 0 时,真正释放锁(Owner 置空 )。

作用:避免嵌套同步场景下的死锁(如递归方法加锁、多层方法调用加锁 )。

五、与内存可见性的关联(Happens-Before 规则)

synchronized 保证 “释放锁”的操作 Happens-Before 于“获取同一锁”的操作

  • 线程 A 释放锁前的所有修改,会强制刷入主内存(内存屏障效果 )。
  • 线程 B 获取同一锁后,可读取到线程 A 释放锁前的最新值,保证可见性

这是 synchronized 实现线程安全的基础(原子性靠锁互斥保证,可见性靠内存屏障保证 )。

六、总结:synchronized 实现逻辑全景

  1. 基础载体:依托对象头的 Mark Word 存储锁状态,Monitor 实现重量级锁的线程调度。
  2. 锁升级:根据竞争程度,从偏向锁(无竞争)→ 轻量级锁(轻度竞争)→ 重量级锁(激烈竞争)动态升级,平衡性能与并发。
  3. 核心能力:通过 Monitor 保证原子性(互斥),通过内存屏障保证可见性,通过重入计数器实现可重入。

简单说,synchronized 是 JVM 深度优化的同步机制:无竞争时用偏向锁加速,轻度竞争时用轻量级锁避免阻塞,激烈竞争时用重量级锁兜底,同时通过对象头、Monitor、锁升级策略,实现高效、安全的线程同步。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值