java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > synchronized JVM层面

synchronized底层原理之JVM层面的锁实现细节与流程

作者:C雨后彩虹

本文从JVM底层视角,详细拆解了synchronized的实现逻辑,涵盖锁的存储载体(对象头的MarkWord)、锁的触发指令(monitorenter/monitorexit指令和ACC_SYNCHRONIZED标志位)以及锁的调度机制,感兴趣的朋友跟随小编一起看看吧

一、前言

在上一篇文章中,我们掌握了其基础用法、核心特性及适用场景,知道它能解决并发编程的原子性、可见性、有序性问题。但你是否好奇:同样是加锁,synchronized为何能实现“隐式管理”?锁的状态是如何存储的?线程之间的锁竞争是如何被JVM调度的?

本文将深入Java虚拟机(JVM)底层,从“锁的载体(对象头)”“锁的触发指令(字节码)”“锁的核心调度机制(monitor)”三个维度,完整拆解synchronized的实现逻辑,带你从“会用”进阶到“懂原理”,真正理解隐式锁的本质。

二、对象头与Mark Word

Java中所有对象都可以作为synchronized的锁对象,这并非偶然——每个Java对象在内存中都包含一个“对象头”结构,而对象头中的“Mark Word”(标记字)正是synchronized锁状态的核心存储载体。简单来说:synchronized的锁,本质是对对象头Mark Word的状态修改与竞争

1. Java对象的内存布局

在HotSpot虚拟机中,Java对象的内存布局分为三部分:

其中,对象头是我们关注的重点,它又分为以下部分:

2. Mark Word的结构与锁状态关联

        Mark Word的结构并非固定不变,而是会根据对象的“锁状态”动态变化——JDK1.6为synchronized引入锁优化后,锁状态分为4种:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态。不同状态下,Mark Word存储的信息不同,目的是在不同并发场景下平衡性能与安全性。

以64位HotSpot虚拟机为例,Mark Word的结构如下图所示:

其中各部分的含义如下:

关键说明:

三、锁的触发与释放

        synchronized是“隐式锁”,其核心优势在于无需手动调用“lock()”“unlock()”方法——这种隐式管理的实现,依赖于JVM在编译阶段为synchronized修饰的代码插入特定的字节码指令。不同用法(修饰方法、修饰代码块)对应的字节码实现略有差异,但核心逻辑一致。

1. 修饰代码块:monitorenter与monitorexit指令

        当synchronized修饰代码块时,JVM会在代码块的“进入处”插入 monitorenter 指令,在“退出处”(包括正常退出和异常退出)插入 monitorexit 指令。这两个指令是锁获取与释放的直接触发者。

代码示例与字节码分析:

public class SyncBlockDemo {
    private final Object lock = new Object();
    public void syncBlock() {
        // 同步代码块
        synchronized (lock) {
            System.out.println("进入同步代码块");
        }
    }
}

使用 javap -v SyncBlockDemo.class 命令反编译,可得到 syncBlock 方法的字节码(核心部分):

public void syncBlock();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=1
         0: aload_0
         1: getfield      #2                  // Field lock:Ljava/lang/Object;
         4: dup
         5: monitorenter  // 进入同步代码块,获取锁(核心指令)
         6: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
         9: ldc           #4                  // String 进入同步代码块
        11: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        14: aload_1
        15: monitorexit   // 正常退出同步代码块,释放锁(核心指令)
        16: goto          24
        19: astore_2
        20: aload_1
        21: monitorexit   // 异常退出同步代码块,释放锁(核心指令)
        22: aload_2
        23: athrow
        24: return
      Exception table:
         from    to  target type
             6    16    19   any
}

字节码核心逻辑解读:

关键结论: monitorenter 和 monitorexit 是synchronized修饰代码块的“锁开关”,JVM通过这两个指令实现锁的获取与释放,且异常场景的锁释放由JVM自动保障。

2. 修饰方法:ACC_SYNCHRONIZED标志位

        当synchronized修饰实例方法或静态方法时,JVM不会插入 monitorenter 和 monitorexit 指令,而是通过在方法的“访问标志(accessflags)”中添加 ACCSYNCHRONIZED 标志位来实现锁机制。

代码示例与字节码分析:

public class SyncMethodDemo {
    // 同步实例方法
    public synchronized void syncInstanceMethod() {
        System.out.println("进入同步实例方法");
    }
    // 同步静态方法
    public static synchronized void syncStaticMethod() {
        System.out.println("进入同步静态方法");
    }
}

反编译后,方法的字节码核心部分如下:

// 同步实例方法
public synchronized void syncInstanceMethod();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_SYNCHRONIZED  // 新增ACC_SYNCHRONIZED标志位
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String 进入同步实例方法
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
// 同步静态方法
public static synchronized void syncStaticMethod();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED  // 新增ACC_SYNCHRONIZED标志位
    Code:
      stack=2, locals=0, args_size=0
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #5                  // String 进入同步静态方法
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return

字节码核心逻辑解读:

关键结论:synchronized修饰方法的锁机制,本质是JVM对 ACC_SYNCHRONIZED 标志位的解析——将锁的获取与释放逻辑嵌入到方法的调用与返回流程中,实现隐式管理。

四、核心锁调度机制

        无论是 monitorenter 指令,还是 ACC_SYNCHRONIZED 标志位,最终都依赖于“monitor”对象实现锁的调度。monitor是操作系统层面的“互斥量(mutex)”的封装,是synchronized重量级锁的核心载体,负责管理线程的锁竞争与等待。

1. monitor的本质与结构

monitor的本质是一个“对象”,在HotSpot虚拟机中,它由C++语言实现(对应 ObjectMonitor 类)。每个Java对象在创建时,都会关联一个monitor对象(可理解为“对象→monitor”的一对一映射),当线程尝试获取锁时,实际上是在竞争monitor的“所有权”。

ObjectMonitor 的核心结构如下(简化版):

class ObjectMonitor {
    // 持有当前monitor的线程(锁的所有者)
    Thread* owner;
    // 等待锁的线程队列(阻塞状态)
    Queue* EntryList;
    // 调用wait()后等待唤醒的线程队列
    Queue* WaitSet;
    // 锁计数器(实现可重入性)
    int count;
    // 递归锁计数器(记录重入次数)
    int recursions;
};

2. 基于monitor的锁竞争流程

        当多个线程竞争同一把锁时,JVM通过monitor的 owner 、 EntryList 、 WaitSet 三个核心组件实现调度,完整流程如下:

3. 重量级锁的性能瓶颈根源

        在JDK1.6之前,synchronized直接使用上述monitor机制(即重量级锁),导致性能较差。其核心瓶颈在于:

这也是JDK1.6引入偏向锁、轻量级锁优化的核心原因——在低并发场景下,避免使用重量级锁的内核态调度,提升锁竞争效率。

五、可重入性的底层实现

        在上一文中我们提到,synchronized具备可重入性——同一线程可以多次获取同一把锁,不会因已持有锁而阻塞。这一特性的底层实现,依赖于monitor的 count (锁计数器)和 recursions (递归锁计数器)。

具体实现逻辑:

示例验证:

public class ReentrantDemo {
    public synchronized void methodA() {
        System.out.println("进入methodA");
        methodB(); // 同一线程调用同步方法,重入锁
    }
    public synchronized void methodB() {
        System.out.println("进入methodB");
    }
    public static void main(String[] args) {
        new ReentrantDemo().methodA();
    }
}

执行流程:

关键结论:可重入性的核心是“锁计数器”——通过计数记录线程的重入次数,确保线程只有在完全释放所有重入锁后,才会让出锁的所有权。

六、内存可见性的底层保障

        在上一文中我们提到,synchronized能保证内存可见性——一个线程对共享变量的修改,会被后续获取同一把锁的线程及时感知。这一特性的底层实现,依赖于JVM在锁的获取与释放过程中插入的“内存屏障”。

1. 内存屏障的核心作用

        内存屏障是CPU层面的指令,用于禁止指令重排序,并强制刷新工作内存与主内存的数据。JVM通过插入内存屏障,确保:

2. synchronized的内存屏障插入规则

JVM为synchronized的锁获取与释放过程,制定了严格的内存屏障插入规则:

3. 与volatile的可见性机制对比

synchronized与volatile都能保证内存可见性,但实现逻辑不同:

特性

synchronized

volatile

可见性实现方式

通过锁获取/释放时插入的内存屏障,间接保证可见性

直接在写操作后插入StoreLoad屏障,读操作前插入LoadLoad屏障,直接保证可见性

额外保障

同时保证原子性、有序性

仅保证可见性、有序性,不保证原子性

适用场景

临界区代码(多操作组合)

单个共享变量的读/写操作

七、总结

        本文从JVM底层视角,完整拆解了synchronized的实现逻辑,核心可总结为“三个核心载体+一个调度机制”:

        理解这些底层原理,能帮你更深刻地理解synchronized的性能特性——为何JDK1.6要引入锁优化?为何偏向锁适合单线程场景?为何高并发下synchronized性能会下降?这些问题,我们将在第3篇“synchronized锁优化深度解析”中详细解答。

        下一篇文章,我们将聚焦JDK1.6的锁优化机制,深入剖析偏向锁、轻量级锁、重量级锁的实现细节与升级流程,带你理解synchronized性能提升的核心逻辑,敬请关注!

到此这篇关于synchronized底层原理之JVM层面的锁实现细节与流程的文章就介绍到这了,更多相关synchronized JVM层面内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文