synchronized实现原理分析 - 字节码层面分析jvm底层如何实现Synchronized

本文详细解析了Java中synchronized关键字的三种使用方式及其底层实现原理,包括实例方法、类方法及代码块的加锁机制,通过字节码分析揭示了JVM如何处理同步方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

开头我们先来复习一下synchronized的使用方法。

第一种在实例方法上使用表示是对当前实例的加锁:

public synchronized void myMethod2(){
        System.out.println("hello world");
    }

第二种是用在类方法上表示对当前类的Class对象加锁:

    public static synchronized void myMethod3(){
        System.out.println("hello world");
    }

第三种就是synchronized代码块。

下面是一个使用的synchronized 的代码块。

public class MyTest1 {
    private   Object obj = new Object();

    public void myMethod(){
        synchronized (obj) {
            System.out.println();
        }
    }
}

我们都知道synchronized 是一个Java的关键字,很多的书中都将它称为内置锁,也叫做监视器锁。它的实现完全是有JVM来实现的。

我们对上面的MyTest1用Javap命令进行反编译

  public void myMethod();
    Code:
       0: aload_0
       1: getfield      #3                  // Field obj:Ljava/lang/Object;
       4: dup
       5: astore_1
       6: monitorenter
       7: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
      10: invokevirtual #5                  // Method java/io/PrintStream.println:()V
      13: aload_1
      14: monitorexit
      15: goto          23
      18: astore_2
      19: aload_1
      20: monitorexit
      21: aload_2
      22: athrow
      23: return

请阅读上面的代码你会防线这个编号6 :有一个monitorenter   以及两个monitorexit  每个锁关联这个一个监视器 当执行指令monitorenter表示的是当前线程获取锁,即就是加锁的操作。monitorexit 表示的是释放锁。

但是为什么这里会有两个monitorexit呢?

因为JVM在执行时为了正常执行和反正异常都能完成锁的释放

当线程请求一个未被持有的锁(执行monitorenter),JVM将记下锁的持有者(获得Monitor对象),并且将获取锁的计数器置为1.如果同一个线程再次获取锁,计数器的值将加1(重入的实现);当执行monitorexit (释放)释放锁计数器减1,当计数器为零,退出代码块

那么synchronized关键次在修饰方法的时候又是如何实现的呢。

public class MyTest2 {
    public  synchronized void printHelloWorld(){
        System.out.println("Hello");
    }
}

 我们使用javap命令编译MyTest2

javap -v MyTest2

得到编译后的结果

 public synchronized void printHelloWorld();
    descriptor: ()V
    flags: ACC_PUBLIC, 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 Hello
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 5: 0
        line 6: 8
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  this   Lcom/crown/concurrency1/MyTest2;

我们在发现我们已经找不到monitorenter跟monitorexit的存在了。

但是在标示flags: 出现了一个ACC _SYNCHRONIZED  JVM使用它来区分方法是否是同步方法, JVM在执行方法时会检查方法是否有ACC_SYNCHRONIZED ,如果有执行线程将会持有方法所在的对象的Monitor对象,在方法执行期间任何线程无法再获取这个Monitor对象,当线程执行完该方法无论是否有异常,都会在方法结束时候释放掉这个Monitor对象。

修饰静态方法字节码分析

public static synchronized void printHelloWorld();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED
    Code:
      stack=2, locals=0, args_size=0
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String Hello
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 5: 0
        line 6: 8

跟非静态的区别是标示多了一个ACC_STATIC  还有就是args_size = 0  上面的args_size  = 1 那么这是为什么呢。因为无论修饰代码块还是修饰方法,方法都是属于该实例的,JVM会在参数中隐式的传入this关键字。当有ACC_STATIC 的时候方法是归Class对象所有,所以参数是零。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值