3.Java并發(fā)synchronized關(guān)鍵字解析

?對于synchronized關(guān)鍵字,作用應(yīng)該都知道保證線程之間的同步性。準(zhǔn)備從以下幾個(gè)方面進(jìn)行講解。

  1. synchronized關(guān)鍵字的用法
  2. synchronized關(guān)鍵字不通用法在指令層面的異同點(diǎn)
  3. synchronized的實(shí)現(xiàn)方式跟鎖膨脹
1. synchronized關(guān)鍵字的用法

? synchronized關(guān)鍵字可以出現(xiàn)在方法簽名跟代碼塊中。可以分為,同步代碼塊,同步方法,靜態(tài)同步方法。這里用代碼進(jìn)行舉例

/**
 * todo: synchronized解析示例
 *
 * @Author acy
 * @Date 2019/06/22
 */
public class SynchronizeTest {
    private static volatile int a = 1;
    private static volatile int b=0;
    @Test
    public void test() throws Exception {
        add();
        sub();
        mul();
        divide();
    }
      //代碼塊,其中代碼塊中的對象為一個(gè)類對象的Class對象最為鎖,這種與synchronized static相同
    private void divide() {
        synchronized (SynchronizeTest.class){
            a=b/a;
        }
    }
        //代碼塊,其中以SynchronizeTest 作為做
    private void mul() {
        synchronized (this){
            b=a*a;
        }
    }
      //同步方法
    private synchronized void sub() {
        a--;
    }
    //靜態(tài)同步方法
    private synchronized static void add() {
        a++;
    }
}

?對于同步代碼塊,當(dāng)對象監(jiān)視器不同(就是括號里面的所對象不同的時(shí)候)不同的同步代碼塊之間不能保證同步。相同的時(shí)候才能保證同步。
?對于使用對象的Class對象的代碼塊,當(dāng)前的所對象是一個(gè)類的class對象,對等于靜態(tài)同步方法 synchronized static。
?對于使用this作為對象監(jiān)視器的代碼塊,當(dāng)前的所對象是當(dāng)前類對象,對等于非靜態(tài)同步方法
(1)synchronized同步方法
1.對于其他synchronized同步方法或synchronized(this)同步代碼塊調(diào)用呈阻塞狀態(tài)
2.同一時(shí)間只有一個(gè)線程可以執(zhí)行synchronized同步方法中的代碼
(2)synchronized(this)同步方法
1.對于其他synchronized同步方法或synchronized(this)同步代碼塊調(diào)用呈阻塞狀態(tài)
2.同一時(shí)間只有一個(gè)線程可以執(zhí)行synchronized(this)同步方法中的代碼
?這里注意獲取到synchronized鎖的線程是無法被打斷的

2. synchronized關(guān)鍵字不通用法在指令層面的異同點(diǎn)

?上面的代碼使用Javap指令編譯成指令之后的結(jié)果為

 private void divide();
    descriptor: ()V
    flags: ACC_PRIVATE
    Code:
      stack=2, locals=3, args_size=1
         0: ldc           #6                  // class com/SynchronizeTest
         2: dup
         3: astore_1
         4: monitorenter
         5: getstatic     #7                  // Field b:I
         8: getstatic     #8                  // Field a:I
        11: idiv
        12: putstatic     #8                  // Field a:I
        15: aload_1
        16: monitorexit
        17: goto          25
        20: astore_2
        21: aload_1
        22: monitorexit
             ......

  private void mul();
    descriptor: ()V
    flags: ACC_PRIVATE
    Code:
      stack=2, locals=3, args_size=1
         0: aload_0
         1: dup
         2: astore_1
         3: monitorenter
         4: getstatic     #8                  // Field a:I
         7: getstatic     #8                  // Field a:I
        10: imul
        11: putstatic     #7                  // Field b:I
        14: aload_1
        15: monitorexit
        16: goto          24
        19: astore_2
        20: aload_1
        21: monitorexit
           ......
  
  private synchronized void sub();
    descriptor: ()V
    flags: ACC_PRIVATE, ACC_SYNCHRONIZED
    Code:
         ......
  
  private static synchronized void add();
    descriptor: ()V
    flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNCHRONIZED
    Code:
         ......

}
1.同步代碼塊

?從上面可以看到對應(yīng)的代碼塊方法里面多了monitorenter跟monitorexit。而且其中monitorexit有兩個(gè)是什么原因呢,下面進(jìn)行說明。

  1. monitorenter跟monitorexit的作用
    ?關(guān)于monitorenter解釋這里引用sun官方的解釋:monitorenter操作的目標(biāo)一定要是一個(gè)對象,類型是reference。Reference實(shí)際就是堆里的一個(gè)存放對象的地 址。每個(gè)對象(reference)都有一個(gè)monitor對應(yīng),如果有其它的線程獲取了這個(gè)對象的monitor,當(dāng)前的線程就要一直等待,直到獲得 monitor的線程放棄monitor,當(dāng)前的線程才有機(jī)會獲得monitor。與之對應(yīng)的monitorexit表示釋放對象的monitor。
  2. 為什么有兩個(gè)monitorexit
    ?第一個(gè)是代碼正常結(jié)束的時(shí)候運(yùn)行的,第二個(gè)是代碼執(zhí)行發(fā)生異常的時(shí)候確保釋放鎖的。
2.同步方法

?在同步方法中沒有看到對應(yīng)的monitorenter跟monitorexit。那是通過什么來獲取鎖對應(yīng)的信息的呢??梢钥吹綄?yīng)的flags上面多了ACC_SYNCHRONIZED標(biāo)志。ACC_SYNCHRONIZED在jvm規(guī)范中是這么解釋的,在調(diào)用synchronized方法時(shí)候,會先去檢查運(yùn)行時(shí)常量池的method_info結(jié)構(gòu)中的access_flags中是否有ACC_SYNCHRONIZED標(biāo)志(method_info是Java對象解析之后保存方法相關(guān)信息的結(jié)構(gòu))。如果ACC_SYNCHRONIZED存在,則執(zhí)行線程進(jìn)入監(jiān)視器,調(diào)用方法本身,并退出監(jiān)視器,方法調(diào)用是正常還是突然完成。在執(zhí)行線程擁有監(jiān)視器期間,沒有其他線程可以輸入它。如果在調(diào)用synchronized方法期間拋出異常并且synchronized方法未處理異常,則在從方法重新拋出異常之前,將自動(dòng)退出該方法的監(jiān)視器synchronized。

3.synchronized的實(shí)現(xiàn)方式跟鎖膨脹
1.Java對象頭

?synchronized用的鎖是存在Java對象頭里面的。如果對象時(shí)數(shù)組類型,則虛擬機(jī)用3個(gè)字存儲對象頭,如果是非數(shù)組類型,則用2字寬存儲對象頭。</br>

長度 內(nèi)容 說明
32/64bit Mark Word 存儲對象的hashCode或鎖信息等
32/64bit Class Metadata Address 存儲到對象類型數(shù)據(jù)的指針
32/64bit Array length 數(shù)組的長度(對象是數(shù)組的時(shí)候)

?Java對象頭里面的Mark Word里面默認(rèn)存儲對象的HashCode,分代年齡和鎖標(biāo)記類型。

2.鎖的升級和對比

?鎖一共有4中狀態(tài),級別從低到高依次是:無所狀態(tài),偏向鎖狀態(tài),輕量級鎖狀態(tài)和重量級鎖狀態(tài),這幾個(gè)狀態(tài)會隨著競爭情況逐漸升級。鎖可以升級但不能降級。這種升級卻不能降級的策略,目的是為了提高獲得鎖和釋放鎖的效率
其中偏向鎖實(shí)在1.6的時(shí)候加上的

鎖狀態(tài) 是否是偏向鎖 鎖標(biāo)志位
無鎖 0 01
偏向鎖 1 01
輕量級鎖 \ 00
重量級鎖 \ 10
  • 偏向鎖

?偏向鎖的作用是,讓同一個(gè)線程再次獲取同一個(gè)鎖的時(shí)候,不用再次去競爭鎖,降低了獲得鎖的代價(jià)。
?當(dāng)一個(gè)線程訪問同步塊并獲取鎖時(shí),會在對象頭和棧幀中的鎖記錄里存儲鎖偏向的線程ID,以后該線程在進(jìn)入和退出同步塊時(shí)候不需要進(jìn)行CAS操作來加鎖和解鎖,只需要簡單的檢測一下對象頭的Mark Word里面是否存儲著指向當(dāng)前線程的偏向鎖。
?測試成功,表示線程已經(jīng)獲得了鎖;測試失敗,則需要再測試一下Mark Word中偏向鎖的標(biāo)識是否設(shè)置成了1,如果沒有設(shè)置,則使用CAS競爭鎖;如果設(shè)置了,則嘗試CAS將對象頭的偏向鎖指向當(dāng)前線程(簡而言之先檢測對象頭的線程ID,有則無后續(xù),沒有就要競爭)
?(1)偏向鎖的撤銷
?偏向鎖使用了一種等到競爭才會出現(xiàn)釋放鎖的機(jī)制,所以當(dāng)其他線程嘗試競爭偏向鎖時(shí),持有偏向鎖的線程才會釋放鎖。偏向鎖的撤銷,需要等待全局安全點(diǎn)。首先會暫停擁有偏向鎖的線程,然后檢查持有偏向鎖的線程是否活著,如果線程不處于活動(dòng)狀態(tài),則會將對象頭設(shè)置為無鎖狀態(tài);如果線程仍然活著,擁有偏向鎖的棧會被執(zhí)行,遍歷偏向?qū)ο蟮逆i記錄,棧中的鎖記錄和對象頭的Mark Word要么重新偏向于其他線程,要么恢復(fù)到無鎖或者標(biāo)記對象不適合作為偏向鎖,最后喚醒暫停的線程
?(2)關(guān)閉偏向鎖
?偏向鎖在Java6和7默認(rèn)是啟用的,但是它在應(yīng)用程序啟動(dòng)幾秒鐘之后才激活,如果有必要可以使用JVM參數(shù)來關(guān)閉延遲:-XX:BiasedLockingStartupDelay=0??梢允褂肑VM參數(shù)-XX:-UseBiasedLocking=false關(guān)閉偏向鎖,那么程序默認(rèn)會進(jìn)入輕量級鎖狀態(tài)


偏向鎖的加鎖跟撤銷
  • 輕量級鎖

?(1)輕量級鎖加鎖
?線程在執(zhí)行同步塊之前,JVM會先在當(dāng)前線程的棧幀中創(chuàng)建用于存儲鎖記錄的空間,并將對象頭中的Mark Word復(fù)制到鎖記錄中。然后線程嘗試使用CAS將對象頭中的Mark Word替換未指向鎖記錄的指針如果成功,當(dāng)前線程獲得鎖,如果失敗,表示其他線程競爭鎖,當(dāng)前線程便會嘗試使用自旋來獲取鎖。
?(2)輕量級鎖解鎖
?輕量級解鎖時(shí),會使用原子的CAS操作將對象頭中的Mark Word替換回到對象頭,如果成功,則表示沒有競爭發(fā)生。如果失敗,表示當(dāng)前鎖存在競爭,鎖就會膨脹為重量級鎖。
?自旋會消耗CPU,為了避免無用的自旋,一旦鎖升級為重量級鎖,就不會再恢復(fù)到輕量級鎖狀態(tài)。當(dāng)鎖處于這個(gè)狀態(tài)下,其他線程試圖獲取鎖時(shí),就會被阻塞,當(dāng)持有鎖的線程釋放鎖之后會喚醒這些線程,進(jìn)行新一輪的鎖的爭奪


無鎖->輕量級->重量級

?關(guān)于CAS后續(xù)的文章會進(jìn)行講解

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容