synchronized 的實現原理

LieBrother發表於2020-04-29

加不加 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 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 鎖。

 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 有什麼區別?

寫了那麼多年 Java 程式碼,終於 debug 到 JVM 了

全網最新最簡單的 openjdk13 程式碼編譯

瞭解Java執行緒優先順序,更要知道對應作業系統的優先順序,不然會踩坑

執行緒最最基礎的知識

老闆叫你別阻塞了

吃個快餐都能學到序列、並行、併發

泡一杯茶,學一學同非同步

程式知多少?

設計模式看了又忘,忘了又看?

後臺回覆『設計模式』可以獲取《一故事一設計模式》電子書

覺得文章有用幫忙轉發&點贊,多謝朋友們!

LieBrother

相關文章