国产片侵犯亲女视频播放_亚洲精品二区_在线免费国产视频_欧美精品一区二区三区在线_少妇久久久_在线观看av不卡

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - Java教程 - 淺說Synchronized的底層實現原理

淺說Synchronized的底層實現原理

2021-01-08 23:26java小當家 Java教程

synchronized關鍵字用來保證在同一時刻只有一個線程可以執行被它修飾的變量或者代碼塊。

 一、前言

synchronized關鍵字用來保證在同一時刻只有一個線程可以執行被它修飾的變量或者代碼塊。

這一篇中,只涉及synchronized的底層實現原理,不涉及對synchronized效率以及如何優化的討論。

二、使用方式

(1)給靜態方法加鎖

public class Main { 

    

    public static synchronized void staticSynPrint(String str) { 

        System.out.println(str); 

    } 

 

靜態方法不屬于任何一個實例,而是屬于該類。不管該類被實例化多少次,靜態成員只有一份。在同一時刻,不管是使用實例.staticSynPrint方式還是直接類名.staticSynPrint的方式,都會進行同步處理。

(2)給靜態變量加鎖

同(1),他們都是該類的靜態成員。

(3)synchronized(xxx.class)

public class Main { 

 

    public void classSynPrint(String str) { 

        synchronized (Main.class) { 

            System.out.println(str); 

        } 

    } 

 

給當前類加鎖(注意是當前類,不是實例對象),會作用于該類的所有實例對象,多個線程訪問Main類中的所有同步方法,都需要先進行同步處理。

(4)synchronized(this)

public class Main { 

 

    public void thisSynPrint(String str) { 

        synchronized (this) { 

            System.out.println(str); 

        } 

    } 

 

this代表實例對象,因此現在鎖住的是當前實例對象,因此多個線程訪問不同實例的同步方法不需要進行同步。

(5)給實例方法加鎖

public class Main { 

 

    public synchronized void synPrint(String str) { 

        System.out.println(str); 

    } 

 

不同線程訪問同一個實例底下的該方法,才會需要進行同步。

三、實際使用方式之一:單例模式中的雙重檢驗鎖

更多單例模式的種類可以參考我的另外一篇博文【設計模式】單例模式

public class SingletonDCL { 

    private volatile static SingletonDCL instance; 

 

    private SingletonDCL() { 

    } 

 

    public static SingletonDCL getInstance() { 

        if (instance == null) { 

            synchronized (Singleton.class) { 

                if (instance == null) { 

                    instance = new SingletonDCL(); 

                } 

            } 

        } 

        return instance; 

    } 

 

有幾個疑問:

(1)這里為什么要檢驗兩次null?

最初的想法,是直接利用synchronized將整個getInstance方法鎖起來,但這樣效率太低,考慮到實際代碼更為復雜,我們應當縮小鎖的范圍。

在單例模式下,要的就是一個單例,new SingletonDCL()只能被執行一次。因此,現在初步考慮成以下的這種方式:

public static SingletonDCL getInstance() { 

       if (instance == null) { 

           synchronized (Singleton.class) { 

                   //一些耗時的操作 

                   instance = new SingletonDCL(); 

           } 

       } 

       return instance; 

   } 

但這樣,存在一個問題。線程1判斷instance為null,然后拿到鎖,執行到了耗時的操作,阻塞了一會兒,還沒有對instance進行實例化,instance還是為null。線程2判斷instance為null,嘗試去獲取鎖。線程1實例化instance之后,釋放了鎖。而線程2獲取鎖之后,同樣進行了實例化操作。線程1和線程2拿到了兩個不同的對象,違背了單例的原則。

因此,在獲取鎖之后,又進行了一次null檢驗。

(2)為什么使用volatile 修飾單例變量?

關于volatie和synchronized的區別,可以先參考我的另外一篇文章【JAVA】volatile和synchronized的區別

這段代碼,instance = new SingletonDCL(),在虛擬機層面,其實分為了3個指令:

為instance分配內存空間,相當于堆中開辟出來一段空間

實例化instance,相當于在上一步開辟出來的空間上,放置實例化好的SingletonDCL對象

將instance變量引用指向第一步開辟出來的空間的首地址

但由于虛擬機做出的某些優化,可能會導致指令重排序,由1->2->3變成1->3->2。這種重新排序在單線程下不會有任何問題,但出于多線程的情況下,可能會出現以下的問題:

線程1獲取鎖之后,執行到了instance = new SingletonDCL()階段,此時,剛好由于虛擬機進行了指令重排序,先進行了第1步開辟內存空間,然后執行了第3步,instance指向空間首地址,第2步還沒來得及執行,此時恰好有線程2執行getInstance方法,最外層判斷instance不為null(instance已經指向了某一段地址,因此不為null),直接返回了單例對象,接著線程2在獲取單例對象屬性的時候,出現了空指針錯誤!

因此使用volatile 修飾單例變量,可以避免由于虛擬機的指令重排序機制可能導致的空指針異常。

四、實現原理

這里可以分兩種情況討論:

(1)同步語句塊

public class Main { 

 

    public static final Object object = new Object(); 

 

    public void print() { 

        synchronized (object) { 

            System.out.println("123"); 

        } 

    } 

 

使用java Main.java,之后使用javap -c Main.class(-c代表反匯編)得到:

public class com.yang.testSyn.Main { 

  public static final java.lang.Object object; 

 

  public com.yang.testSyn.Main(); 

    Code: 

       0: aload_0 

       1: invokespecial #1                  // Method java/lang/Object."<init>":()V 

       4: return 

 

  public void print(); 

    Code: 

       0: getstatic     #2                  // Field object:Ljava/lang/Object; 

       3: dup 

       4: astore_1 

       5: monitorenter 

       6: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream; 

       9: ldc           #4                  // String 123 

      11: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V 

      14: aload_1 

      15: monitorexit 

      16: goto          24 

      19: astore_2 

      20: aload_1 

      21: monitorexit 

      22: aload_2 

      23: athrow 

      24: return 

    Exception table

       from    to  target type 

           6    16    19   any 

          19    22    19   any 

 

  static {}; 

    Code: 

       0: new           #6                  // class java/lang/Object 

       3: dup 

       4: invokespecial #1                  // Method java/lang/Object."<init>":()V 

       7: putstatic     #2                  // Field object:Ljava/lang/Object; 

      10: return 

其中print方法中的第5行、15行出現了monitorenter和monitorexit,而這兩行其中的字節碼代表的正是同步語句塊里的內容。

當線程執行到monitorenter時,代表即將進入到同步語句塊中,線程首先需要去獲得Object的對象鎖,而對象鎖處于每個java對象的對象頭中,對象頭中會有一個鎖的計數器,當線程查詢對象頭中計數器,發現內容為0時,則代表該對象沒有被任何線程所占有,此時該線程可以占有此對象,計數器于是加1。

線程占有該對象后,也就是拿到該對象的鎖,可以執行同步語句塊里面的方法。此時,如果有其他線程進來,查詢對象頭發現計數器不為0,于是進入該對象的鎖等待隊列中,一直阻塞到計數器為0時,方可繼續執行。

第一個線程執行到enterexit后,釋放了Object的對象鎖,此時第二個線程可以繼續執行。

這邊依然有幾個問題:

[1]為什么有一個monitorenter指令,卻有兩個monitorexit指令?

因為編譯器必須保證,無論同步代碼塊中的代碼以何種方式結束(正常 return 或者異常退出),代碼中每次調用 monitorenter 必須執行對應的 monitorexit 指令。為了保證這一點,編譯器會自動生成一個異常處理器,這個異常處理器的目的就是為了同步代碼塊拋出異常時能執行 monitorexit。這也是字節碼中,只有一個 monitorenter 卻有兩個 monitorexit 的原因。

當然這一點,也可以從Exception table(異常表)中看出來,字節碼中第6(from)到16(to)的偏移量中如果出現任何類型(type)的異常,都會跳轉到第19(target)行。

(2)同步方法

public class Main { 

 

    public synchronized void print(String str) { 

        System.out.println(str); 

    } 

 

使用javap -v Main.class查看

-v 選項可以顯示更加詳細的內容,比如版本號、類訪問權限、常量池相關的信息,是一個非常有用的參數。

public class com.yang.testSyn.Main 

  minor version: 0 

  major version: 52 

  flags: ACC_PUBLIC, ACC_SUPER 

Constant pool: 

   #1 = Methodref          #5.#14         // java/lang/Object."<init>":()V 

   #2 = Fieldref           #15.#16        // java/lang/System.out:Ljava/io/PrintStream; 

   #3 = Methodref          #17.#18        // java/io/PrintStream.println:(Ljava/lang/String;)V 

   #4 = Class              #19            // com/yang/testSyn/Main 

   #5 = Class              #20            // java/lang/Object 

   #6 = Utf8               <init> 

   #7 = Utf8               ()V 

   #8 = Utf8               Code 

   #9 = Utf8               LineNumberTable 

  #10 = Utf8               print 

  #11 = Utf8               (Ljava/lang/String;)V 

  #12 = Utf8               SourceFile 

  #13 = Utf8               Main.java 

  #14 = NameAndType        #6:#7          // "<init>":()V 

  #15 = Class              #21            // java/lang/System 

  #16 = NameAndType        #22:#23        // out:Ljava/io/PrintStream; 

  #17 = Class              #24            // java/io/PrintStream 

  #18 = NameAndType        #25:#11        // println:(Ljava/lang/String;)V 

  #19 = Utf8               com/yang/testSyn/Main 

  #20 = Utf8               java/lang/Object 

  #21 = Utf8               java/lang/System 

  #22 = Utf8               out 

  #23 = Utf8               Ljava/io/PrintStream; 

  #24 = Utf8               java/io/PrintStream 

  #25 = Utf8               println 

  public com.yang.testSyn.Main(); 

    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 

 

  public synchronized void print(java.lang.String); 

    descriptor: (Ljava/lang/String;)V 

    flags: ACC_PUBLIC, ACC_SYNCHRONIZED 

    Code: 

      stack=2, locals=2, args_size=2 

         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream; 

         3: aload_1 

         4: invokevirtual #3                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V 

         7: return 

      LineNumberTable: 

        line 32: 0 

        line 33: 7 

只看最后兩個方法,第一個方法是編譯后自動生成的默認構造方法,第二個方法則是我們的同步方法,可以看到同步方法比默認的構造方法多了一個ACC_SYNCHRONIZED的標志位。

與同步語句塊不同,虛擬機不會在字節碼層面實現鎖同步,而是會先觀察該方法是否含有ACC_SYNCHRONIZED標志。如果含有,則線程會首先嘗試獲取鎖。如果是實例方法,則會嘗試獲取實例鎖;如果是靜態方法(類方法),則會嘗試獲取類鎖。最后不管方法執行是否出現異常,都會釋放鎖。

 

原文地址:https://www.toutiao.com/i6914914346254533123/

延伸 · 閱讀

精彩推薦
Weibo Article 1 Weibo Article 2 Weibo Article 3 Weibo Article 4 Weibo Article 5 Weibo Article 6 Weibo Article 7 Weibo Article 8 Weibo Article 9 Weibo Article 10 Weibo Article 11 Weibo Article 12 Weibo Article 13 Weibo Article 14 Weibo Article 15 Weibo Article 16 Weibo Article 17 Weibo Article 18 Weibo Article 19 Weibo Article 20 Weibo Article 21 Weibo Article 22 Weibo Article 23 Weibo Article 24 Weibo Article 25 Weibo Article 26 Weibo Article 27 Weibo Article 28 Weibo Article 29 Weibo Article 30 Weibo Article 31 Weibo Article 32 Weibo Article 33 Weibo Article 34 Weibo Article 35 Weibo Article 36 Weibo Article 37 Weibo Article 38 Weibo Article 39 Weibo Article 40
主站蜘蛛池模板: 亚洲国产精品一区二区第一页 | 一级黄色一级黄色 | 欧美成人免费 | 黄网站色大毛片 | 天天天干天天天操 | 欧美一区二区三区在线视频观看 | 国产精品乱码人人做人人爱 | 久久中文字幕一区 | 一级黄色片在线 | 黄色片视频免费在线观看 | 亚洲国产高清在线 | 国产精品美乳一区二区免费 | 欧美一区二区三区在线播放 | 午夜精品福利在线观看 | 国产精品久久久久久久久 | 成人精品久久久 | 国产精品欧美久久久 | 国产一区二区三区四区在线观看 | 一区二区中文 | 亚洲伊人久久综合 | 欧美性大战久久久 | 欧美日韩久久精品 | 久久精品久久久久久久久久16 | 欧美国产日韩一区二区三区 | 久久艹色| 精品国偷自产国产一区 | 日韩一区二区三区电影在线观看 | 黄色片免费在线 | 伊人久久综合 | a免费在线 | 日韩视频精品在线 | 韩日一区二区三区 | 久久久成人精品 | 日韩欧美大片在线观看 | av在线一区二区三区 | 米奇影视7777| 一区二区三区四区在线 | 中文字幕日韩欧美 | 天堂久久久久久 | 龙珠z国语版291集全 | 欧美a级网站 |