synchronized 的实现原理

  |   0 评论   |   146 浏览

    前面 3 篇文章讲了 synchronized 的同步方法和同步代码块两种用法,还有锁实例对象和锁 Class 对象两种锁机制。今天我们来看看同步方法和同步代码块的实现原理。

    我们把前 3 篇有涉及到的 synchronized 方法全写在一起,如下面所示。

    public class SynchronizedPrincipleTest {
    
        public void testNoSynchronized() {
            System.out.println("hello testNoSynchronized");
        }
    
        public synchronized void testSynchronizedMethod() {
            System.out.println("hello testSynchronizedMethod");
        }
    
        public static synchronized void testSynchronizedStatic() {
            System.out.println("hello testSynchronizedStatic");
        }
    
        public void testSynchronizedCodethis() {
            synchronized (this) {
                System.out.println("hello testSynchronizedCode");
            }
        }
    
        private Object lock = new Object();
        public void testSynchronizedCodeObject() {
            synchronized (lock) {
                System.out.println("hello testSynchronizedCodeObject");
            }
        }
    
        public void testSynchronizedCodeClass() {
            synchronized (SynchronizedPrincipleTest.class) {
                System.out.println("hello testSynchronizedCodeClass");
            }
        }
    }
    

    编写好代码之后,我们通过 javac 命令编译代码,使用 javap 命令反编译出汇编代码出来。命令如下所示。

    javac SynchronizedPrincipleTest.java
    javap -v SynchronizedCodeTest.class
    

    得出我们要汇编代码。

    Classfile /D:/Workspace/finance/test/thread/src/main/java/com/liebrother/study/synchronizeds/SynchronizedPrincipleTest.class
      Last modified Apr 26, 2020; size 1363 bytes
      MD5 checksum a03ec0b152580bb465b1defe7965a60d
      Compiled from "SynchronizedPrincipleTest.java"
    public class com.liebrother.study.synchronizeds.SynchronizedPrincipleTest
      minor version: 0
      major version: 52
      flags: ACC_PUBLIC, ACC_SUPER
    Constant pool:
       #1 = Methodref          #2.#31         // java/lang/Object."<init>":()V
       #2 = Class              #32            // java/lang/Object
       #3 = Fieldref           #11.#33        // com/liebrother/study/synchronizeds/SynchronizedPrincipleTest.lock:Ljava/lang/Object;
       #4 = Fieldref           #34.#35        // java/lang/System.out:Ljava/io/PrintStream;
       #5 = String             #36            // hello testNoSynchronized
       #6 = Methodref          #37.#38        // java/io/PrintStream.println:(Ljava/lang/String;)V
       #7 = String             #39            // hello testSynchronizedMethod
       #8 = String             #40            // hello testSynchronizedStatic
       #9 = String             #41            // hello testSynchronizedCode
      #10 = String             #42            // hello testSynchronizedCodeObject
      #11 = Class              #43            // com/liebrother/study/synchronizeds/SynchronizedPrincipleTest
      #12 = String             #44            // hello testSynchronizedCodeClass
      #13 = Utf8               lock
      #14 = Utf8               Ljava/lang/Object;
      #15 = Utf8               <init>
      #16 = Utf8               ()V
      #17 = Utf8               Code
      #18 = Utf8               LineNumberTable
      #19 = Utf8               testNoSynchronized
      #20 = Utf8               testSynchronizedMethod
      #21 = Utf8               testSynchronizedStatic
      #22 = Utf8               testSynchronizedCodethis
      #23 = Utf8               StackMapTable
      #24 = Class              #43            // com/liebrother/study/synchronizeds/SynchronizedPrincipleTest
      #25 = Class              #32            // java/lang/Object
      #26 = Class              #45            // java/lang/Throwable
      #27 = Utf8               testSynchronizedCodeObject
      #28 = Utf8               testSynchronizedCodeClass
      #29 = Utf8               SourceFile
      #30 = Utf8               SynchronizedPrincipleTest.java
      #31 = NameAndType        #15:#16        // "<init>":()V
      #32 = Utf8               java/lang/Object
      #33 = NameAndType        #13:#14        // lock:Ljava/lang/Object;
      #34 = Class              #46            // java/lang/System
      #35 = NameAndType        #47:#48        // out:Ljava/io/PrintStream;
      #36 = Utf8               hello testNoSynchronized
      #37 = Class              #49            // java/io/PrintStream
      #38 = NameAndType        #50:#51        // println:(Ljava/lang/String;)V
      #39 = Utf8               hello testSynchronizedMethod
      #40 = Utf8               hello testSynchronizedStatic
      #41 = Utf8               hello testSynchronizedCode
      #42 = Utf8               hello testSynchronizedCodeObject
      #43 = Utf8               com/liebrother/study/synchronizeds/SynchronizedPrincipleTest
      #44 = Utf8               hello testSynchronizedCodeClass
      #45 = Utf8               java/lang/Throwable
      #46 = Utf8               java/lang/System
      #47 = Utf8               out
      #48 = Utf8               Ljava/io/PrintStream;
      #49 = Utf8               java/io/PrintStream
      #50 = Utf8               println
      #51 = Utf8               (Ljava/lang/String;)V
    {
      public com.liebrother.study.synchronizeds.SynchronizedPrincipleTest();
        descriptor: ()V
        flags: ACC_PUBLIC
        Code:
          stack=3, locals=1, args_size=1
             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 lock:Ljava/lang/Object;
            15: return
          LineNumberTable:
            line 7: 0
            line 27: 4
    
      /** 无 synchronized 修饰的代码 */
      public void testNoSynchronized();
        descriptor: ()V
        flags: ACC_PUBLIC
        Code:
          stack=2, locals=1, args_size=1
             0: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
             3: ldc           #5                  // String hello testNoSynchronized
             5: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
             8: return
          LineNumberTable:
            line 10: 0
            line 11: 8
    
      /** synchronized 修饰的实例方法 */
      public synchronized void testSynchronizedMethod();
        descriptor: ()V
        flags: ACC_PUBLIC, ACC_SYNCHRONIZED /** 方法标识多了一个 ACC_SYNCHRONIZED */
        Code:
          stack=2, locals=1, args_size=1
             0: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
             3: ldc           #7                  // String hello testSynchronizedMethod
             5: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
             8: return
          LineNumberTable:
            line 14: 0
            line 15: 8
    
      /** synchronized 修饰的静态方法 */
      public static synchronized void testSynchronizedStatic();
        descriptor: ()V
        flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED /** 方法标识多了 ACC_STATIC 和 ACC_SYNCHRONIZED */
        Code:
          stack=2, locals=0, args_size=0
             0: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
             3: ldc           #8                  // String hello testSynchronizedStatic
             5: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
             8: return
          LineNumberTable:
            line 18: 0
            line 19: 8
    
      /** synchronized 修饰的 this 代码块 */
      public void testSynchronizedCodethis();
        descriptor: ()V
        flags: ACC_PUBLIC
        Code:
          stack=2, locals=3, args_size=1
             0: aload_0
             1: dup
             2: astore_1
             3: monitorenter     /** 通过 monitorenter 命令进入监视器锁  */
             4: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
             7: ldc           #9                  // String hello testSynchronizedCode
             9: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
            12: aload_1
            13: monitorexit      /** 通过 monitorexit 命令退出监视器锁  */
            14: goto          22
            17: astore_2
            18: aload_1
            19: monitorexit      /** 通过 monitorexit 命令退出监视器锁  */
            20: aload_2
            21: athrow
            22: return
          Exception table:
             from    to  target type
                 4    14    17   any
                17    20    17   any
          LineNumberTable:
            line 22: 0
            line 23: 4
            line 24: 12
            line 25: 22
          StackMapTable: number_of_entries = 2
            frame_type = 255 /* full_frame */
              offset_delta = 17
              locals = [ class com/liebrother/study/synchronizeds/SynchronizedPrincipleTest, class java/lang/Object ]
              stack = [ class java/lang/Throwable ]
            frame_type = 250 /* chop */
              offset_delta = 4
    
      /** synchronized 修饰的 object 代码块 */
      public void testSynchronizedCodeObject();
        descriptor: ()V
        flags: ACC_PUBLIC
        Code:
          stack=2, locals=3, args_size=1
             0: aload_0
             1: getfield      #3                  // Field lock:Ljava/lang/Object;
             4: dup
             5: astore_1
             6: monitorenter      /** 通过 monitorenter 命令进入监视器锁  */
             7: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
            10: ldc           #10                 // String hello testSynchronizedCodeObject
            12: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
            15: aload_1
            16: monitorexit       /** 通过 monitorexit 命令退出监视器锁  */
            17: goto          25
            20: astore_2
            21: aload_1
            22: monitorexit       /** 通过 monitorexit 命令退出监视器锁  */
            23: aload_2
            24: athrow
            25: return
          Exception table:
             from    to  target type
                 7    17    20   any
                20    23    20   any
          LineNumberTable:
            line 29: 0
            line 30: 7
            line 31: 15
            line 32: 25
          StackMapTable: number_of_entries = 2
            frame_type = 255 /* full_frame */
              offset_delta = 20
              locals = [ class com/liebrother/study/synchronizeds/SynchronizedPrincipleTest, class java/lang/Object ]
              stack = [ class java/lang/Throwable ]
            frame_type = 250 /* chop */
              offset_delta = 4
    
      /** synchronized 修饰的 xxx.Class 代码块 */
      public void testSynchronizedCodeClass();
        descriptor: ()V
        flags: ACC_PUBLIC
        Code:
          stack=2, locals=3, args_size=1
             0: ldc           #11                 // class com/liebrother/study/synchronizeds/SynchronizedPrincipleTest
             2: dup
             3: astore_1
             4: monitorenter       /** 通过 monitorenter 命令进入监视器锁  */
             5: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
             8: ldc           #12                 // String hello testSynchronizedCodeClass
            10: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
            13: aload_1
            14: monitorexit       /** 通过 monitorexit 命令退出监视器锁  */
            15: goto          23
            18: astore_2
            19: aload_1
            20: monitorexit       /** 通过 monitorexit 命令退出监视器锁  */
            21: aload_2
            22: athrow
            23: return
          Exception table:
             from    to  target type
                 5    15    18   any
                18    21    18   any
          LineNumberTable:
            line 35: 0
            line 36: 5
            line 37: 13
            line 38: 23
          StackMapTable: number_of_entries = 2
            frame_type = 255 /* full_frame */
              offset_delta = 18
              locals = [ class com/liebrother/study/synchronizeds/SynchronizedPrincipleTest, class java/lang/Object ]
              stack = [ class java/lang/Throwable ]
            frame_type = 250 /* chop */
              offset_delta = 4
    }
    SourceFile: "SynchronizedPrincipleTest.java"
    

    这段代码有点多,加了些注释方便大家看,这里我抽一些重要的点讲一下。

    1) 我们可以看到同步方法和同步代码块的同步实现不太一样。

    同步方法的实现是在方法标识 flags 中加了 ACC_SYNCHRONIZED 标识,是一种隐式实现,具体是 JVM 在执行方法的时候,检查是否有 ACC_SYNCHRONIZED 同步标识,有的话会等待获取监控器 monitor,然后在方法执行结束时释放监控器 monitor。

    同步代码块的实现是在加同步代码块前加上 monitorenter 指令,在同步代码块后加上 monitorexit 指令,每个对象都有一个 monitor 监视器,当 monitor 被某线程占用了,该线程就锁定了该 monitor。每个 monitor 都维护一个自己的计数器,当执行 monitorenter 时,该计数器 +1,当执行 monitorexit 时候释放锁,计数器变为 0。其他线程才可以尝试获得 monitor,对共享资源进行操作。

    2) 同步实例方法 testSynchronizedMethod() 和同步静态方法 testSynchronizedStatic() 差别只是在于 flags 有没有 ACC_STATIC 标识,其实锁实例对象还是锁 Class 对象,也是 JVM 底层实现根据这个标识去做判断,对我们来说是透明的。

    3) 同步代码块锁什么对象 this VS object VS xxx.class,在这个汇编代码可以看出来的。

    this 的代码如下。在进入 monitor 监听器前,先获取 this 对象,也就是进入 this 对象的 monitor 锁。

     0: aload_0        /** 加载当前 this 对象 */
     1: dup            /** 将 this 对象压入栈顶 */
     2: astore_1       /** 从栈顶取出 this 对象 */
     3: monitorenter   /** 获取 this 对象的 monitor 锁 */
    

    object 的代码如下。在进入 monitor 监听器前,先获取 lock 对象,也就是进入 lock 对象的 monitor 锁。

    0: aload_0          /** 加载当前 this 对象 */
    1: getfield      #3 /** 获取 this 对象的实例变量 lock */                 // Field lock:Ljava/lang/Object;
    4: dup              /** 将实例变量 lock 压入栈顶 */
    5: astore_1         /** 从栈顶取出 lock 对象 */
    6: monitorenter     /** 获取 lock 对象的 monitor 锁 */
    

    xxx.class 的代码如下。在进入 monitor 监听器前,先获取 Class 对象,也就是进入 Class 对象的 monitor 锁。

    0: ldc           #11 /** 从常量池中获取 SynchronizedPrincipleTest 类对象 */                 // class com/liebrother/study/synchronizeds/SynchronizedPrincipleTest
    2: dup               /** 将 Class 对象压入栈顶 */
    3: astore_1          /** 从栈顶取出 Class 对象 */
    4: monitorenter      /** 获取 Class 对象的 monitor 锁 */
    

    今天从 Java 的汇编代码来分析同步方法和同步代码块的底层实现,其实这块还不算是真正的底层实现,只是站在 Java 层面上来说,这已经是最底层了。站在 JVM 这是最高层,接下来会从 JVM 角度来分析为什么同步方法加上 ACC_SYNCHRONIZED 和 同步代码块加上 monitorenter & monitorexit 就可以实现多线程同步?

    悄悄打个预防针,接下来的文章会有些晦涩难懂,但是我觉得很有必要弄懂它,弄懂了最底层原理,那么多线程就不怕了,弄懂了,后面会给大家讲的 AQS 就很容易懂,它是把 JVM 底层的实现搬到 Java 源库。

    原创不易,大家多点个赞,非常感谢!

    推荐阅读

    synchronized 作为悲观锁,锁住了什么?

    加不加 synchronized 有什么区别?

    从 JVM 视角看看 Java 守护线程

    写了那么多年 Java 代码,终于 debug 到 JVM 了

    全网最新最简单的 openjdk13 代码编译

    了解Java线程优先级,更要知道对应操作系统的优先级,不然会踩坑

    线程最最基础的知识

    老板叫你别阻塞了

    吃个快餐都能学到串行、并行、并发

    泡一杯茶,学一学同异步

    进程知多少?

    设计模式看了又忘,忘了又看?

    后台回复『设计模式』可以获取《一故事一设计模式》电子书

    觉得文章有用帮忙转发&点赞,多谢朋友们!

    LieBrother

    评论

    发表评论

    validate