synchronized 实现原理

2020-05-25 10:50:43 浏览数 (1)

加不加 synchronized 有什么区别?

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

synchronized 代码块怎么用?

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

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

代码语言:javascript复制
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 命令反编译出汇编代码出来。命令如下所示。

代码语言:javascript复制
javac SynchronizedPrincipleTest.java
javap -v SynchronizedCodeTest.class

得出我们要汇编代码。

代码语言:javascript复制
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,对共享资源进行操作。

  1. 同步实例方法 testSynchronizedMethod() 和同步静态方法 testSynchronizedStatic() 差别只是在于 flags 有没有 ACC_STATIC 标识,其实锁实例对象还是锁 Class 对象,也是 JVM 底层实现根据这个标识去做判断,对我们来说是透明的。
  2. 同步代码块锁什么对象 this VS object VS xxx.class,在这个汇编代码可以看出来的。

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

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

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

代码语言:javascript复制
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 锁。

代码语言:javascript复制
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 源库。

0 人点赞