JVM字节码分析--Synchronized关键字底层实现

    xiaoxiao2024-12-02  69

    JVM字节码分析--Synchronized关键字底层实现

    一、代码块级别的 synchronized二、方法级别的synchronized

    一、代码块级别的 synchronized

    一个代码块加synchronized关键字的例子:

    private Object lock = new Object(); public void test() { synchronized (lock) { foo(); } } public void foo() { }

    字节码反编译:

    public void test(); Code: 0: aload_0 1: getfield #3 // private Object lock = new Object(); 4: dup 5: astore_1 6: monitorenter 7: aload_0 8: invokevirtual #4 // Method foo:()V 11: aload_1 12: monitorexit 13: goto 21 16: astore_2 17: aload_1 18: monitorexit 19: aload_2 20: athrow 21: return Exception table: from to target type 7 13 16 any 16 19 16 any

    代码块的同步是通过 monitorenter 和 monitorexit 两个指令来实现 synchronized 关键字的。上面的字节码中

    0 ~ 5:将 lock 对象入栈,使用 dup 指令复制栈顶元素,并将它存入局部变量表位置 1 的地方,现在栈上还剩下一个 lock 对象

    6:以栈顶元素 lock 做为锁,使用 monitorenter 开始同步

    7 ~ 8:调用 foo() 方法

    11 ~ 12:将 lock 对象入栈,调用 monitorexit 释放锁

    16 ~ 20:执行异常处理,字节码会自动加上这段,因为编译器必须保证,无论同步代码块中的代码以何种方式结束(正常 return 或者异常退出),代码中每次调用 monitorenter 必须执行对应的 monitorexit 指令。为了保证这一点,编译器会自动生成一个异常处理器,这个异常处理器的目的就是为了同步代码块抛出异常时能执行 monitorexit。这也是字节码中,只有一个 monitorenter 却有两个 monitorexit 的原因。

    总而言之,调用monitorenter指令后,先将lock出栈(解锁)。调用monitoexit指令退出后,再将lock入栈(上锁),如遇到异常则强制monitorexit退出。

    二、方法级别的synchronized

    方法级别的同步由常量池中方法的 ACC_SYNCHRONIZED 标志来隐式实现。

    synchronized public void test() { }

    字节码反编译:

    public synchronized void test(); descriptor: ()V flags: ACC_PUBLIC, ACC_SYNCHRONIZED

    JVM 不会使用特殊的字节码来调用同步方法,当 JVM 解析方法的符号引用时,它会判断方法是不是同步的(检查方法 ACC_SYNCHRONIZED 是否被设置)。如果是,执行线程会先尝试获取锁。如果是实例方法,JVM 会尝试获取实例对象的锁,如果是类方法,JVM 会尝试获取类锁。在同步方法完成以后,不管是正常返回还是异常返回,都会释放锁。

    最新回复(0)