通过字节码去理解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成功其他线程则无法获取。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1. synchronized关键字在使用层面的理解 synchronized关键字Java中用来实现线程同步关键字,可以修饰方法和代码块。当线程访问被synchronized修饰的方法或代码块时,需要获取对象的锁,如果该锁已被其他线程获取,则该线程会进入阻塞状态,直到获取到锁为止。synchronized关键字可以保证同一时刻只有一个线程能够访问被锁定的方法或代码块,从而避免了多线程并发访问时的数据竞争和一致性问题。 2. synchronized关键字字节码中的体现 在Java代码编译成字节码后,synchronized关键字会被编译成monitorenter和monitorexit指令来实现。monitorenter指令对应获取锁操作,monitorexit指令对应释放锁操作。 3. synchronized关键字在JVM中的实现 在JVM中,每个对象都有一个监视器(monitor),用来实现对象锁。当一个线程获取对象锁后,就进入了对象的监视器中,其他线程只能等待该线程释放锁后再去竞争锁。 synchronized关键字的实现涉及到对象头中的标志位,包括锁标志位和重量级锁标志位等。当一个线程获取锁后,锁标志位被设置为1,其他线程再去获取锁时,会进入自旋等待或者阻塞等待状态,直到锁标志位被设置为0,即锁被释放后才能获取锁。 4. synchronized关键字在硬件方面的实现 在硬件层面,锁的实现需要通过CPU指令和总线锁来实现。当一个线程获取锁时,CPU会向总线发送一个锁请求信号,其他CPU收到该信号后会进入自旋等待状态,直到锁被释放后才能获取锁。总线锁可以保证多个CPU之间的原子操作,从而保证锁的正确性和一致性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值