通过字节码去理解synchronized关键字

通过字节码去理解synchronized 关键字

  • 我们都知道synchronized是java提供一个关键字,这个关键字可以实现线程的同步保护共享资源。这个保护归根结底是由谁来实现呢?大家应该都知道是jvm,那jvm是如何确保线程安全的呢?本文的重点就是来学习这一块的知识点。下面我们通过几个简单的代码从字节码的角度分析synchronized的原理。

synchronized修饰代码块

  • 代码如下所示:
/**
 * 描述:  synchronized 修饰代码块
 *
 * @author karl
 * @create 2020-01-30 13:50
 */
public class MySynchronizedTest04 {
    Object object = new Object();

    public void method() {
        synchronized (object) {
            System.out.println("hello world");
        }
    }
}
  • 找到class文件的所在位置执行javap -c 即可查看字节码文件内容,字节码如下所示
public class com.karl.concurrent.syn.MySynchronizedTest04 {
  java.lang.Object object;

  public com.karl.concurrent.syn.MySynchronizedTest04();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: aload_0
       5: new           #2                  // class java/lang/Object
       8: dup
       9: invokespecial #1                  // Method java/lang/Object."<init>":()V
      12: putfield      #3                  // Field object:Ljava/lang/Object;
      15: return
  ## 上述就是MySynchronizedTest04的构造方法的字节码,不是我们的讲述重点在这里就不做过多的讲解
  public void method();
  ## 字节码文件中有很多的助记符 不用刻意去背,遇到了再去查看就行
    Code:
       0: aload_0
       1: getfield      #3                  // Field object:Ljava/lang/Object;    ## getfield 助记符 获取当对象的成员变量 object
       4: dup
       5: astore_1
       6: monitorenter                      ## monitorenter 这一行就是比较重要对应synchronized的语义
       7: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;  ## getstatic 获取静态成员变量 out
      10: ldc           #5                  // String hello world
      12: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      15: aload_1
      16: monitorexit                       ## monitorexit 对应上面的monitorenter 一进一出 
      17: goto          25                  ## 进入25 25return 方法执行结束 这个是正常的释放锁的过程
      ## 为保证线程能够释放锁 不管是正常结束还是异常报错都能够释放锁 下面就是出现了异常场景 释放锁的过程。所以monitorenter 一般会对应两个monitorexit 一个正常退出一个异常退出
      20: astore_2
      21: aload_1
      22: monitorexit
      23: aload_2
      24: athrow
      25: return
      ## 这里是异常表 
    Exception table:
       from    to  target type
           7    17    20   any
          20    23    20   any
}

  • 当线程进入monitorenter指令后,线程就会持有monitor对象,当线程退出monitorexit指令后就会释放monitor对象。这就是获取到锁和释放锁的过程。

synchronized修饰普通的实例方法

  • 代码如下所示
/**
 * 描述:  synchronized 修饰方法
 *
 * @author karl
 * @create 2020-01-30 15:06
 */
public class MySynchronizedTest05 {
    
    public synchronized void method() {
        System.out.println("hello world");
    }
}
  • 通过javap -v 查看更多字节码信息 如下所示
public synchronized void method();
    descriptor: ()V                  ## 无参无返回值
    flags: ACC_PUBLIC, ACC_SYNCHRONIZED  ## 标志位  中有ACC_SYNCHRONIZED 
    Code:
    ## 在code中我们没有发现 monitorenter monitorexit 
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String hello world
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 13: 0
        line 14: 8
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  this   Lcom/karl/concurrent/syn/MySynchronizedTest05;
}
SourceFile: "MySynchronizedTest05.java"
  • 当synchronized修饰一个代码块和修饰一个方法的时候,它的实现是不一样的。synchronized在修饰方法的时候是通过方法的标志位flags 是否含有ACC_SYNCHRONIZED 来区分是否为同步方法。
  • 当线程准备调用这个方法的时候,首先要坚持当前方法的标志位是否含有ACC_SYNCHRONIZED如果含有ACC_SYNCHRONIZED,当前线程尝试去获取当前对象的锁,获取成功之后再调用该方法,方法执行完毕后释放当前对象的锁。

synchronized修饰静态方法

  • 代码如下所示
/**
 * 描述:  synchronized 修饰静态方法
 *
 * @author karl
 * @create 2020-01-30 19:00
 */
public class MySynchronizedTest06 {

    public synchronized static void method() {
        System.out.println("hello world");
    }
}
  • 通过javap -v 查看更多字节码信息 如下所示
public static synchronized void method();
    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 world
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 12: 0
        line 13: 8
}
SourceFile: "MySynchronizedTest06.java"
  • 通过我们观察和修饰普通方法唯一的区别就是在访问标志位多了ACC_STATIC,表面该方法为静态方法,而且通过上述分析ACC_SYNCHRONIZED可知是同步的静态方法。
  • 当线程准备调用这个方法的时候,首先要坚持当前方法的标志位是否含有ACC_SYNCHRONIZED如果含有ACC_SYNCHRONIZED,且含有ACC_STATIC当前线程尝试去获取当前的锁,获取成功之后再调用该方法,方法执行完毕后释放当前对象的锁。

总结

  • jvm的同步是基于进入退出monitor对象(管程)来实现的。每个java对象的实例都会有一个monitor对象,monitor对象与java对象一同创建/销毁。monitor是由C++来管理的。
  • 当多线程同时访问同一段代码块时,这些线程会被放到一个entryList集合中,处于阻塞状态的线程都会被放置到该list中。接下来当线程获取到对象的monitor对象时,Monitor是依赖操作系统底层mutex lock锁来实现互斥的。线程获取到mutex lock成功其他线程则无法获取。
发布了59 篇原创文章 · 获赞 30 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/qq_33249725/article/details/104117975
今日推荐