Java synchronized 关键字实现原理

使用的java版本

src git:(master) ✗ java -version 
java version "1.8.0_201"
Java(TM) SE Runtime Environment (build 1.8.0_201-b09)
Java HotSpot(TM) 64-Bit Server VM (build 25.201-b09, mixed mode)

先说结论:

  1. 对于同步方法,JVM采用ACC_SYNCHRONIZED标记符来实现同步。
  2. 对于同步代码块,JVM采用monitorenter、monitorexit两个指令来实现同步。

同步方法

public class MainTest {

    public synchronized void test() {
            System.out.println("Hello world");
    }

}

我们使用javap -v命令来查看class对应的字节码

javap -v MainTest

MainTest.class对应的字节码的部分代码

public class com.hm.sync_test.MainTest

{

  public synchronized void test();
    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 world
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 34: 0
        line 35: 8
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  this   Lcom/hm/sync_test/MainTest;
}

在上面的代码中我们可以看到test方法有两个flag。

 flags: ACC_PUBLIC, ACC_SYNCHRONIZED

ACC_PUBLIC的意思是access public,标记可见性是public。
ACC_SYNCHRONIZED的意思是access synchronized,标记是一个synchronized方法。

对于同步方法,JVM采用ACC_SYNCHRONIZED标记符来实现同步。

方法级的同步是隐式的。同步方法会有一个ACC_SYNCHRONIZED标志。当某个线程要访问某个设置了ACC_SYNCHRONIZED标志的方法的时,执行线程需要先获得监视器,然后开始执行方法,方法执行之后再释放监视器。这时如果其他线程来请求执行方法,会因为无法获得监视器而被阻断住。值得注意的是,如果在方法执行过程中,发生了异常,并且方法内部并没有处理该异常,那么在异常被抛到方法外面之前监视器会被自动释放。

同步代码块

public class MainTest {

    public void test() {
        //使用类对象作为锁
        synchronized (MainTest.class){
            System.out.println("Hello world");
        }
    }
}

MainTest.class对应的字节码的部分代码

public class com.hm.sync_test.MainTest

{

  public void test();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=1
         0: ldc           #2                  // class com/hm/sync_test/MainTest
         2: dup
         3: astore_1
         4: monitorenter
         5: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
         8: ldc           #4                  // String Hello world
        10: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        13: aload_1
        14: monitorexit
        15: goto          23
        18: astore_2
        19: aload_1
        20: monitorexit
        21: aload_2
        22: athrow
        23: return
}

对于同步代码块。JVM采用monitorenter、monitorexit两个指令来实现同步。

同步代码块使用monitorenter和monitorexit两个指令实现。可以把执行monitorenter指令理解为加锁,执行monitorexit理解为释放锁。 每个对象维护着一个记录着被锁次数的计数器。未被锁定的对象的该计数器为0,当一个线程获得锁(执行monitorenter)后,该计数器自增变为 1 ,当同一个线程再次获得该对象的锁的时候,计数器再次自增。当同一个线程释放锁(执行monitorexit指令)的时候,计数器再自减。当计数器为0的时候。锁将被释放,其他线程便可以获得锁。

  1. 当一个线程进行执行的遇到monitorenter指令的时候,它会去尝试获得锁,如果没有获得锁,那么阻塞。
  2. 当一个线程进行执行的遇到monitorenter指令的时候,它会去尝试获得锁,如果获得锁,那么锁计数+1(为什么会加1呢,因为它是一个可重入锁,所以需要用这个锁计数判断锁的情况)。当它遇到monitorexit的时候,锁计数器-1,当计数器为0的时候就释放锁。

我们注意到,上面的代码中有2个monitorexit。第14: monitorexit和第20: monitorexit行。

这是因为synchronized锁释放有两种机制,一种就是执行完释放;另外一种就是抛出异常,虚拟机释放。图中第2个monitorexit就是发生异常时执行的流程。
而且,从图中我们也可以看到在第15行,有一个goto指令15: goto 23,也就是说如果正常运行结束会跳转到23行直接返回了。

参考链接:

  • 详解synchronized与Lock的区别与使用
  • Java锁–Lock实现原理(底层实现)
  • 再有人问你synchronized是什么,就把这篇文章发给他。
  • 一文带你理解 Java 中 Lock 的实现原理
  • Chapter 2. The Structure of the Java Virtual Machine
  • Chapter 6. The Java Virtual Machine Instruction Set

你可能感兴趣的:(Java基础)