併發王者課-青銅7:順藤摸瓜-如何從synchronized中的鎖認識Monitor

秦二爺發表於2021-05-28

在前面的文章中,我們已經體驗過synchronized的用法,並對鎖的概念和原理做了簡單的介紹。然而,你可能已經察覺到,有一個概念似乎總是和synchronized這兩個概念如影相隨,很多人也比較喜歡問它們之間的區別,這個概念就是Monitor,也叫監視器

所以,在講解完synchronized、鎖之後,文字將為你講解Monitor,揭示它們之間那些公開的祕密,希望你不再迷惑。

首先,你要明白的是,Monitor作為一種同步機制,它並非Java所特有,但Java實現了這一機制。

為了具象地理解Monitor這一抽象概念,我們先來分析身邊的一個常見場景。

一、從醫院排隊就診機制理解Monitor

相信你一定有過去醫院就診的經歷。我們去醫院時,情況一般是這樣的:

  • 首先,我們在門診大廳前臺或自助掛號機進行掛號
  • 隨後,掛號結束後我們找到對應的診室就診
    • 診室每次只能有一個患者就診;
    • 如果此時診室空閒,直接進入就診;
    • 如果此時診室內有患者正在就診,那麼我們進入候診室,等待叫號;
  • 就診結束後,走出就診室,候診室的下一位候診患者進入就診室。

這個就診過程你一定耳熟能詳,理解起來必然毫不費力氣。我們做了一張圖展示圖下:

仔細看這幅圖中的就診過程,如果你理解了這個過程,你就理解了Monitor. 這麼簡單嗎?不要懷疑自己,是的。你竟然早已理解Monitor機制

不要小看這個機制,它可是生活中的智慧體現。在這個就診機制中,它起到了兩個關鍵性的作用

  • 互斥(mutual exclusion ):每次只允許一個患者進入候診室就診;
  • 協作(cooperation):就診室中的患者就診結束後,可以通知候診區的下一位患者。

明白了嗎?你在醫院就診的過程竟然和Monitor的機制幾乎一模一樣。我們換個方式來描述Monitor在電腦科學中的作用:

  • 互斥(mutual exclusion ):每次只允許一個執行緒進入臨界區;
  • 協作(cooperation):當臨界區的執行緒執行結束後滿足特定條件時,可以通知其他的等待執行緒進入。

而就診過程中的門診大廳就診室候診室則恰好對應著Monitor中的三個關鍵概念。其中:

  • 門診大廳:所有待進入的執行緒都必須先在入口(Entry Set)掛號才有資格;
  • 就診室:一個每次只能有一個執行緒進入的特殊房間(Special Room)
  • 候診室:就診室繁忙時,進入等待區(Wait Set)

我們把上面的圖稍作調整,就可以看到Monitor在Java中的模樣:

對比來看,相信你已經很直觀地理解Monitor機制。再一回味,你會發現synchronized正是對Monitor機制的一種實現。而在Java中,每一個物件都會關聯一個監視器

二、從synchronized原始碼感受Monitor

既然synchronized是對Monitor機制的一種實現,為了讓你更有體感,我們可以寫一段極簡程式碼一探究竟。

這段程式碼極為簡單,但是夠用,我們在程式碼中使用了synchronized關鍵字:

public class SyncMonitorDemo {
    public static void main(String[] args) {
        Object o = new Object();
        synchronized (o) {
            System.out.println("locking...");
        }
    }
}

程式碼寫好後,分別執行javac SyncMonitorDemo.javajavap -v SyncMonitorDemo.class,隨後你就能得到下面這樣的位元組碼:

Classfile SyncMonitorDemo.class
  Last modified May 26, 2021; size 684 bytes
  MD5 checksum e366920f22845e98c45f26531596d6cf
  Compiled from "SyncMonitorDemo.java"
public class cn.tao.king.juc.execises1.SyncMonitorDemo
  minor version: 0
  major version: 49
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #2.#22         // java/lang/Object."<init>":()V
   #2 = Class              #23            // java/lang/Object
   #3 = Fieldref           #24.#25        // java/lang/System.out:Ljava/io/PrintStream;
   #4 = String             #26            // locking...
   #5 = Methodref          #27.#28        // java/io/PrintStream.println:(Ljava/lang/String;)V
   #6 = Class              #29            // cn/tao/king/juc/execises1/SyncMonitorDemo
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               LocalVariableTable
  #12 = Utf8               this
  #13 = Utf8               Lcn/tao/king/juc/execises1/SyncMonitorDemo;
  #14 = Utf8               main
  #15 = Utf8               ([Ljava/lang/String;)V
  #16 = Utf8               args
  #17 = Utf8               [Ljava/lang/String;
  #18 = Utf8               o
  #19 = Utf8               Ljava/lang/Object;
  #20 = Utf8               SourceFile
  #21 = Utf8               SyncMonitorDemo.java
  #22 = NameAndType        #7:#8          // "<init>":()V
  #23 = Utf8               java/lang/Object
  #24 = Class              #30            // java/lang/System
  #25 = NameAndType        #31:#32        // out:Ljava/io/PrintStream;
  #26 = Utf8               locking...
  #27 = Class              #33            // java/io/PrintStream
  #28 = NameAndType        #34:#35        // println:(Ljava/lang/String;)V
  #29 = Utf8               cn/tao/king/juc/execises1/SyncMonitorDemo
  #30 = Utf8               java/lang/System
  #31 = Utf8               out
  #32 = Utf8               Ljava/io/PrintStream;
  #33 = Utf8               java/io/PrintStream
  #34 = Utf8               println
  #35 = Utf8               (Ljava/lang/String;)V
{
  public cn.tao.king.juc.execises1.SyncMonitorDemo();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 3: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcn/tao/king/juc/execises1/SyncMonitorDemo;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=4, args_size=1
         0: new           #2                  // class java/lang/Object
         3: dup
         4: invokespecial #1                  // Method java/lang/Object."<init>":()V
         7: astore_1
         8: aload_1
         9: dup
        10: astore_2
        11: monitorenter
        12: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
        15: ldc           #4                  // String locking...
        17: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        20: aload_2
        21: monitorexit
        22: goto          30
        25: astore_3
        26: aload_2
        27: monitorexit
        28: aload_3
        29: athrow
        30: return
      Exception table:
         from    to  target type
            12    22    25   any
            25    28    25   any
      LineNumberTable:
        line 5: 0
        line 6: 8
        line 7: 12
        line 8: 20
        line 9: 30
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      31     0  args   [Ljava/lang/String;
            8      23     1     o   Ljava/lang/Object;
}
SourceFile: "SyncMonitorDemo.java"

javap是JDK自帶的一個反彙編命令。你可以忽略其他不必要的資訊,直接在結果中找到下面這段程式碼:

11: monitorenter
12: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
15: ldc           #4                  // String locking...
17: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
20: aload_2
21: monitorexit

看到monitorentermonitorexit指令,相信智慧的你已經看穿一切。

以上就是文字的全部內容,恭喜你又上了一顆星✨

夫子的試煉

  • 寫一段包含synchronized關鍵字的程式碼,使用javap命令觀察結果。

關於作者

關注公眾號【庸人技術笑談】,獲取及時文章更新。記錄平凡人的技術故事,分享有品質(儘量)的技術文章,偶爾也聊聊生活和理想。不販賣焦慮,不兜售課程。

如果本文對你有幫助,歡迎點贊關注

相關文章