synchronized 的实现原理

1,095 阅读21分钟

加不加 synchronized 有什么区别?

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

synchronized 代码块怎么用?

前面 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 262020; size 1363 bytes
  MD5 checksum a03ec0b152580bb465b1defe7965a60d
  Compiled from "SynchronizedPrincipleTest.java"
public class com.liebrother.study.synchronizeds.SynchronizedPrincipleTest
  minor version: 0
  major version: 52
  flagsACC_PUBLICACC_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
         5new           #2                  // class java/lang/Object
         8: dup
         9: invokespecial #1                  // Method java/lang/Object."<init>":()V
        12: putfield      #3                  // Field lock:Ljava/lang/Object;
        15return
      LineNumberTable:
        line 70
        line 274

  /** 无 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
         8return
      LineNumberTable:
        line 100
        line 118

  /** 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
         8return
      LineNumberTable:
        line 140
        line 158

  /** 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
         8return
      LineNumberTable:
        line 180
        line 198

  /** 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
        22return
      Exception table:
         from    to  target type
             4    14    17   any
            17    20    17   any
      LineNumberTable:
        line 220
        line 234
        line 2412
        line 2522
      StackMapTable: number_of_entries = 2
        frame_type = 255 /* full_frame */
          offset_delta = 17
          locals = [ class com/liebrother/study/synchronizeds/SynchronizedPrincipleTestclass 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
        25return
      Exception table:
         from    to  target type
             7    17    20   any
            20    23    20   any
      LineNumberTable:
        line 290
        line 307
        line 3115
        line 3225
      StackMapTable: number_of_entries = 2
        frame_type = 255 /* full_frame */
          offset_delta = 20
          locals = [ class com/liebrother/study/synchronizeds/SynchronizedPrincipleTestclass 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
        23return
      Exception table:
         from    to  target type
             5    15    18   any
            18    21    18   any
      LineNumberTable:
        line 350
        line 365
        line 3713
        line 3823
      StackMapTable: number_of_entries = 2
        frame_type = 255 /* full_frame */
          offset_delta = 18
          locals = [ class com/liebrother/study/synchronizeds/SynchronizedPrincipleTestclass 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 锁。

 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 作为悲观锁,锁住了什么?

加不加 synchronized 有什么区别?

从 JVM 视角看看 Java 守护线程

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

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

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

线程最最基础的知识

老板叫你别阻塞了

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

泡一杯茶,学一学同异步

进程知多少?

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

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

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

LieBrother