日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
淺說Synchronized的底層實(shí)現(xiàn)原理

一、前言

創(chuàng)新互聯(lián)專注為客戶提供全方位的互聯(lián)網(wǎng)綜合服務(wù),包含不限于網(wǎng)站設(shè)計(jì)、成都做網(wǎng)站、富寧網(wǎng)絡(luò)推廣、微信小程序開發(fā)、富寧網(wǎng)絡(luò)營銷、富寧企業(yè)策劃、富寧品牌公關(guān)、搜索引擎seo、人物專訪、企業(yè)宣傳片、企業(yè)代運(yùn)營等,從售前售中售后,我們都將竭誠為您服務(wù),您的肯定,是我們最大的嘉獎(jiǎng);創(chuàng)新互聯(lián)為所有大學(xué)生創(chuàng)業(yè)者提供富寧建站搭建服務(wù),24小時(shí)服務(wù)熱線:028-86922220,官方網(wǎng)址:www.cdcxhl.com

synchronized關(guān)鍵字用來保證在同一時(shí)刻只有一個(gè)線程可以執(zhí)行被它修飾的變量或者代碼塊。

這一篇中,只涉及synchronized的底層實(shí)現(xiàn)原理,不涉及對(duì)synchronized效率以及如何優(yōu)化的討論。

二、使用方式

(1)給靜態(tài)方法加鎖

 
 
 
  1. public class Main { 
  2.     
  3.     public static synchronized void staticSynPrint(String str) { 
  4.         System.out.println(str); 
  5.     } 
  6.  

 靜態(tài)方法不屬于任何一個(gè)實(shí)例,而是屬于該類。不管該類被實(shí)例化多少次,靜態(tài)成員只有一份。在同一時(shí)刻,不管是使用實(shí)例.staticSynPrint方式還是直接類名.staticSynPrint的方式,都會(huì)進(jìn)行同步處理。

(2)給靜態(tài)變量加鎖

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

(3)synchronized(xxx.class)

 
 
 
  1. public class Main { 
  2.  
  3.     public void classSynPrint(String str) { 
  4.         synchronized (Main.class) { 
  5.             System.out.println(str); 
  6.         } 
  7.     } 
  8.  

 給當(dāng)前類加鎖(注意是當(dāng)前類,不是實(shí)例對(duì)象),會(huì)作用于該類的所有實(shí)例對(duì)象,多個(gè)線程訪問Main類中的所有同步方法,都需要先進(jìn)行同步處理。

(4)synchronized(this)

 
 
 
  1. public class Main { 
  2.  
  3.     public void thisSynPrint(String str) { 
  4.         synchronized (this) { 
  5.             System.out.println(str); 
  6.         } 
  7.     } 
  8.  

 this代表實(shí)例對(duì)象,因此現(xiàn)在鎖住的是當(dāng)前實(shí)例對(duì)象,因此多個(gè)線程訪問不同實(shí)例的同步方法不需要進(jìn)行同步。

(5)給實(shí)例方法加鎖

 
 
 
  1. public class Main { 
  2.  
  3.     public synchronized void synPrint(String str) { 
  4.         System.out.println(str); 
  5.     } 
  6.  

 不同線程訪問同一個(gè)實(shí)例底下的該方法,才會(huì)需要進(jìn)行同步。

三、實(shí)際使用方式之一:單例模式中的雙重檢驗(yàn)鎖

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

 
 
 
  1. public class SingletonDCL { 
  2.     private volatile static SingletonDCL instance; 
  3.  
  4.     private SingletonDCL() { 
  5.     } 
  6.  
  7.     public static SingletonDCL getInstance() { 
  8.         if (instance == null) { 
  9.             synchronized (Singleton.class) { 
  10.                 if (instance == null) { 
  11.                     instance = new SingletonDCL(); 
  12.                 } 
  13.             } 
  14.         } 
  15.         return instance; 
  16.     } 
  17.  

 有幾個(gè)疑問:

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

最初的想法,是直接利用synchronized將整個(gè)getInstance方法鎖起來,但這樣效率太低,考慮到實(shí)際代碼更為復(fù)雜,我們應(yīng)當(dāng)縮小鎖的范圍。

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

 
 
 
  1. public static SingletonDCL getInstance() { 
  2.        if (instance == null) { 
  3.            synchronized (Singleton.class) { 
  4.                    //一些耗時(shí)的操作 
  5.                    instance = new SingletonDCL(); 
  6.            } 
  7.        } 
  8.        return instance; 
  9.    } 

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

因此,在獲取鎖之后,又進(jìn)行了一次null檢驗(yàn)。

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

關(guān)于volatie和synchronized的區(qū)別,可以先參考我的另外一篇文章【JAVA】volatile和synchronized的區(qū)別

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

為instance分配內(nèi)存空間,相當(dāng)于堆中開辟出來一段空間

實(shí)例化instance,相當(dāng)于在上一步開辟出來的空間上,放置實(shí)例化好的SingletonDCL對(duì)象

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

但由于虛擬機(jī)做出的某些優(yōu)化,可能會(huì)導(dǎo)致指令重排序,由1->2->3變成1->3->2。這種重新排序在單線程下不會(huì)有任何問題,但出于多線程的情況下,可能會(huì)出現(xiàn)以下的問題:

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

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

四、實(shí)現(xiàn)原理

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

(1)同步語句塊

 
 
 
  1. public class Main { 
  2.  
  3.     public static final Object object = new Object(); 
  4.  
  5.     public void print() { 
  6.         synchronized (object) { 
  7.             System.out.println("123"); 
  8.         } 
  9.     } 
  10.  

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

 
 
 
  1. public class com.yang.testSyn.Main { 
  2.   public static final java.lang.Object object; 
  3.  
  4.   public com.yang.testSyn.Main(); 
  5.     Code: 
  6.        0: aload_0 
  7.        1: invokespecial #1                  // Method java/lang/Object."":()V 
  8.        4: return 
  9.  
  10.   public void print(); 
  11.     Code: 
  12.        0: getstatic     #2                  // Field object:Ljava/lang/Object; 
  13.        3: dup 
  14.        4: astore_1 
  15.        5: monitorenter 
  16.        6: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream; 
  17.        9: ldc           #4                  // String 123 
  18.       11: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V 
  19.       14: aload_1 
  20.       15: monitorexit 
  21.       16: goto          24 
  22.       19: astore_2 
  23.       20: aload_1 
  24.       21: monitorexit 
  25.       22: aload_2 
  26.       23: athrow 
  27.       24: return 
  28.     Exception table: 
  29.        from    to  target type 
  30.            6    16    19   any 
  31.           19    22    19   any 
  32.  
  33.   static {}; 
  34.     Code: 
  35.        0: new           #6                  // class java/lang/Object 
  36.        3: dup 
  37.        4: invokespecial #1                  // Method java/lang/Object."":()V 
  38.        7: putstatic     #2                  // Field object:Ljava/lang/Object; 
  39.       10: return 

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

當(dāng)線程執(zhí)行到monitorenter時(shí),代表即將進(jìn)入到同步語句塊中,線程首先需要去獲得Object的對(duì)象鎖,而對(duì)象鎖處于每個(gè)java對(duì)象的對(duì)象頭中,對(duì)象頭中會(huì)有一個(gè)鎖的計(jì)數(shù)器,當(dāng)線程查詢對(duì)象頭中計(jì)數(shù)器,發(fā)現(xiàn)內(nèi)容為0時(shí),則代表該對(duì)象沒有被任何線程所占有,此時(shí)該線程可以占有此對(duì)象,計(jì)數(shù)器于是加1。

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

第一個(gè)線程執(zhí)行到enterexit后,釋放了Object的對(duì)象鎖,此時(shí)第二個(gè)線程可以繼續(xù)執(zhí)行。

這邊依然有幾個(gè)問題:

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

因?yàn)榫幾g器必須保證,無論同步代碼塊中的代碼以何種方式結(jié)束(正常 return 或者異常退出),代碼中每次調(diào)用 monitorenter 必須執(zhí)行對(duì)應(yīng)的 monitorexit 指令。為了保證這一點(diǎn),編譯器會(huì)自動(dòng)生成一個(gè)異常處理器,這個(gè)異常處理器的目的就是為了同步代碼塊拋出異常時(shí)能執(zhí)行 monitorexit。這也是字節(jié)碼中,只有一個(gè) monitorenter 卻有兩個(gè) monitorexit 的原因。

當(dāng)然這一點(diǎn),也可以從Exception table(異常表)中看出來,字節(jié)碼中第6(from)到16(to)的偏移量中如果出現(xiàn)任何類型(type)的異常,都會(huì)跳轉(zhuǎn)到第19(target)行。

(2)同步方法

 
 
 
  1. public class Main { 
  2.  
  3.     public synchronized void print(String str) { 
  4.         System.out.println(str); 
  5.     } 
  6.  

 使用javap -v Main.class查看

-v 選項(xiàng)可以顯示更加詳細(xì)的內(nèi)容,比如版本號(hào)、類訪問權(quán)限、常量池相關(guān)的信息,是一個(gè)非常有用的參數(shù)。

 
 
 
  1. public class com.yang.testSyn.Main 
  2.   minor version: 0 
  3.   major version: 52 
  4.   flags: ACC_PUBLIC, ACC_SUPER 
  5. Constant pool: 
  6.    #1 = Methodref          #5.#14         // java/lang/Object."":()V 
  7.    #2 = Fieldref           #15.#16        // java/lang/System.out:Ljava/io/PrintStream; 
  8.    #3 = Methodref          #17.#18        // java/io/PrintStream.println:(Ljava/lang/String;)V 
  9.    #4 = Class              #19            // com/yang/testSyn/Main 
  10.    #5 = Class              #20            // java/lang/Object 
  11.    #6 = Utf8                
  12.    #7 = Utf8               ()V 
  13.    #8 = Utf8               Code 
  14.    #9 = Utf8               LineNumberTable 
  15.   #10 = Utf8               print 
  16.   #11 = Utf8               (Ljava/lang/String;)V 
  17.   #12 = Utf8               SourceFile 
  18.   #13 = Utf8               Main.java 
  19.   #14 = NameAndType        #6:#7          // "":()V 
  20.   #15 = Class              #21            // java/lang/System 
  21.   #16 = NameAndType        #22:#23        // out:Ljava/io/PrintStream; 
  22.   #17 = Class              #24            // java/io/PrintStream 
  23.   #18 = NameAndType        #25:#11        // println:(Ljava/lang/String;)V 
  24.   #19 = Utf8               com/yang/testSyn/Main 
  25.   #20 = Utf8               java/lang/Object 
  26.   #21 = Utf8               java/lang/System 
  27.   #22 = Utf8               out 
  28.   #23 = Utf8               Ljava/io/PrintStream; 
  29.   #24 = Utf8               java/io/PrintStream 
  30.   #25 = Utf8               println 
  31.   public com.yang.testSyn.Main(); 
  32.     descriptor: ()V 
  33.     flags: ACC_PUBLIC 
  34.     Code: 
  35.       stack=1, locals=1, args_size=1 
  36.          0: aload_0 
  37.          1: invokespecial #1                  // Method java/lang/Object."":()V 
  38.          4: return 
  39.       LineNumberTable: 
  40.         line 3: 0 
  41.  
  42.   public synchronized void print(java.lang.String); 
  43.     descriptor: (Ljava/lang/String;)V 
  44.     flags: ACC_PUBLIC, ACC_SYNCHRONIZED 
  45.     Code: 
  46.       stack=2, locals=2, args_size=2 
  47.          0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream; 
  48.          3: aload_1 
  49.          4: invokevirtual #3                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V 
  50.          7: return 
  51.       LineNumberTable: 
  52.         line 32: 0 
  53.         line 33: 7 

 只看最后兩個(gè)方法,第一個(gè)方法是編譯后自動(dòng)生成的默認(rèn)構(gòu)造方法,第二個(gè)方法則是我們的同步方法,可以看到同步方法比默認(rèn)的構(gòu)造方法多了一個(gè)ACC_SYNCHRONIZED的標(biāo)志位。

與同步語句塊不同,虛擬機(jī)不會(huì)在字節(jié)碼層面實(shí)現(xiàn)鎖同步,而是會(huì)先觀察該方法是否含有ACC_SYNCHRONIZED標(biāo)志。如果含有,則線程會(huì)首先嘗試獲取鎖。如果是實(shí)例方法,則會(huì)嘗試獲取實(shí)例鎖;如果是靜態(tài)方法(類方法),則會(huì)嘗試獲取類鎖。最后不管方法執(zhí)行是否出現(xiàn)異常,都會(huì)釋放鎖。


新聞名稱:淺說Synchronized的底層實(shí)現(xiàn)原理
網(wǎng)站網(wǎng)址:http://www.dlmjj.cn/article/dheipep.html