亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

深入剖析Java中的synchronized關(guān)鍵字

 更新時(shí)間:2023年06月18日 14:26:16   作者:半畝方塘立身  
在 Java 程序中,我們可以利用 synchronized 關(guān)鍵字來(lái)對(duì)程序進(jìn)行加鎖,它既可以用來(lái)聲明一個(gè) synchronized 代碼塊,也可以直接標(biāo)記靜態(tài)方法或者實(shí)例方法,本文就帶大家深入了解Java中的synchronized關(guān)鍵字,感興趣的同學(xué)可以參考閱讀

synchronized介紹

synchronized關(guān)鍵字可以解決的是多個(gè)線程之間訪問(wèn)資源的同步性。synchronized關(guān)鍵字可以保證被它修飾的方法或者代碼塊在任意時(shí)刻只能有一個(gè)線程執(zhí)行。

在 Java 程序中,我們可以利用 synchronized 關(guān)鍵字來(lái)對(duì)程序進(jìn)行加鎖。它既可以用來(lái)聲明一個(gè) synchronized 代碼塊,也可以直接標(biāo)記靜態(tài)方法或者實(shí)例方法。

關(guān)鍵字在代碼塊上

代碼如下:

public void methodA() {
    Object obj = new Object();
    synchronized (obj) {
        //
    }
}

編譯結(jié)果(javap -v)

public void methodA();
descriptor: ()V
flags: ACC_PUBLIC
Code:
  stack=2, locals=4, args_size=1
     0: new           #3                  // 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: aload_2
    13: monitorexit
    14: goto          22
    17: astore_3
    18: aload_2
    19: monitorexit
    20: aload_3
    21: athrow
    22: return
  Exception table:
     from    to  target type
        12    14    17   any
        17    20    17   any

上面的字節(jié)碼中包含一個(gè) monitorenter 指令以及多個(gè) monitorexit 指令。這是因?yàn)?Java 虛擬機(jī)需要確保所獲得的鎖在正常執(zhí)行路徑,以及異常執(zhí)行路徑上都能夠被解鎖。 synchronized 應(yīng)用在同步塊上時(shí),在字節(jié)碼中是通過(guò) monitorenter 和 monitorexit 實(shí)現(xiàn)的。

關(guān)鍵字在方法上

代碼如下:

public synchronized void methodB() {  
    //  
    i++;  
}

當(dāng)synchronized修飾同步方法時(shí),編譯器會(huì)在生成的字節(jié)碼中添加一個(gè)額外的指令來(lái)獲取和釋放方法的監(jiān)視器鎖(monitor lock)。同時(shí),編譯器還會(huì)設(shè)置方法的ACC_SYNCHRONIZED標(biāo)志。

public synchronized void methodB();
  descriptor: ()V
  flags: ACC\_PUBLIC, ACC\_SYNCHRONIZED
  Code:
    stack=3, locals=1, args\_size=1
        0: aload\_0
        1: dup
        2: getfield      #2                  // Field i:I
        5: iconst\_1
        6: iadd
        7: putfield      #2                  // Field i:I
        10: return
        LineNumberTable:
        line 15: 0
        line 16: 10

當(dāng)JVM加載字節(jié)碼文件并解析類(lèi)的時(shí)候,會(huì)檢查方法的訪問(wèn)標(biāo)志。如果ACC_SYNCHRONIZED標(biāo)志被設(shè)置,表示在進(jìn)入該方法時(shí),Java 虛擬機(jī)需要進(jìn)行 monitorenter 操作。而在退出該方法時(shí),不管是正常返回,還是向調(diào)用者拋異常,Java 虛擬機(jī)均需要進(jìn)行 monitorexit 操作。

這里 monitorenter 和 monitorexit 操作所對(duì)應(yīng)的鎖對(duì)象是隱式的。對(duì)于實(shí)例方法來(lái)說(shuō),這兩個(gè)操作對(duì)應(yīng)的鎖對(duì)象是 this;對(duì)于靜態(tài)方法來(lái)說(shuō),這兩個(gè)操作對(duì)應(yīng)的鎖對(duì)象則是所在類(lèi)的 Class 實(shí)例。

synchronized原理

synchronized 對(duì)對(duì)象進(jìn)行加鎖,在 JVM 中,對(duì)象在內(nèi)存中分為三塊區(qū)域:對(duì)象頭、實(shí)例數(shù)據(jù)和對(duì)齊填充。在對(duì)象頭中保存了鎖標(biāo)志位和指向 monitor 對(duì)象的起始地址,如下圖所示,右側(cè)就是對(duì)象對(duì)應(yīng)的 Monitor 對(duì)象。當(dāng) Monitor 被某個(gè)線程持有后,就會(huì)處于鎖定狀態(tài),如圖中的 Owner 部分,會(huì)指向持有 Monitor 對(duì)象的線程。另外 Monitor 中還有兩個(gè)隊(duì)列,用來(lái)存放進(jìn)入及等待獲取鎖的線程。

為了提升性能,JDK1.6 引入了偏向鎖、輕量級(jí)鎖、重量級(jí)鎖概念,來(lái)減少鎖競(jìng)爭(zhēng)帶來(lái)的上下文切換,而正是新增的 Java 對(duì)象頭實(shí)現(xiàn)了鎖升級(jí)功能。當(dāng) Java 對(duì)象被 Synchronized 關(guān)鍵字修飾成為同步鎖后,圍繞這個(gè)鎖的一系列升級(jí)操作都將和 Java 對(duì)象頭有關(guān)。

對(duì)象頭

Java中對(duì)象頭由三個(gè)部分組成:Mark Word、Klass Pointer、Length。

  • Mark Word
    Mark WordMark Word記錄了與對(duì)象和鎖相關(guān)的信息,當(dāng)這個(gè)對(duì)象作為鎖對(duì)象來(lái)實(shí)現(xiàn)synchronized的同步操作時(shí),鎖標(biāo)記和相關(guān)信息都是存儲(chǔ)在Mark Word中的。

    64位系統(tǒng)Mark Word存儲(chǔ)結(jié)構(gòu)如下:

  • 從圖中可以看到一個(gè)鎖狀態(tài)的字段,它包含五種狀態(tài)分別是無(wú)鎖、偏向鎖、輕量級(jí)鎖、重量級(jí)鎖、GC標(biāo)記。通過(guò)1bit來(lái)表達(dá)無(wú)鎖和偏向鎖,其中0表示無(wú)鎖、1表示偏向鎖。

  • Klass PointerKlass Pointer
    Klass PointerKlass Pointer表示指向類(lèi)的指針,JVM通過(guò)這個(gè)指針來(lái)確定對(duì)象具體屬于哪個(gè)類(lèi)的實(shí)例。它的存儲(chǔ)長(zhǎng)度根據(jù)JVM的位數(shù)來(lái)決定,在32位的虛擬機(jī)中占4字節(jié),在64位的虛擬機(jī)中占8字節(jié),但是在JDK 1.8中,由于默認(rèn)開(kāi)啟了指針壓縮,所以壓縮后在64位系統(tǒng)中只占4字節(jié)。

  • Length
    Length表示數(shù)組長(zhǎng)度,只有構(gòu)建對(duì)象數(shù)組時(shí)才會(huì)有數(shù)組長(zhǎng)度屬性。

鎖升級(jí)過(guò)程

當(dāng)一個(gè)線程訪問(wèn)增加了synchronized關(guān)鍵字的代碼塊時(shí),如果偏向鎖是開(kāi)啟狀態(tài),則先嘗試通過(guò)偏向鎖來(lái)獲得鎖資源,這個(gè)過(guò)程僅僅通過(guò)CAS來(lái)完成。如果當(dāng)前已經(jīng)有其他線程獲得了偏向鎖,那么搶占鎖資源的線程由于無(wú)法獲得鎖,所以會(huì)嘗試升級(jí)到輕量級(jí)鎖來(lái)進(jìn)行鎖資源搶占,輕量級(jí)鎖就是通過(guò)多次CAS(也就是自旋鎖)來(lái)完成的。如果這個(gè)線程通過(guò)多次自旋仍然無(wú)法獲得鎖資源,那么最終只能升級(jí)到重量級(jí)鎖來(lái)實(shí)現(xiàn)線程的等待。

下圖顯示了對(duì)象頭的布局和不同對(duì)象狀態(tài)的表示:

偏向鎖的原理

偏向鎖其實(shí)可以認(rèn)為是在沒(méi)有多線程競(jìng)爭(zhēng)的情況下訪問(wèn)synchronized修飾的代碼塊的加鎖場(chǎng)景,也就是在單線程執(zhí)行的情況下。

實(shí)際上對(duì)程序開(kāi)發(fā)來(lái)說(shuō),加鎖是為了防范線程安全性的風(fēng)險(xiǎn),但是是否有線程競(jìng)爭(zhēng)并不由我們來(lái)控制,而是由應(yīng)用場(chǎng)景來(lái)決定。假設(shè)這種情況存在,就沒(méi)有必要使用重量級(jí)鎖基于操作系統(tǒng)級(jí)別的Mutex Lock來(lái)實(shí)現(xiàn)鎖的搶占,這樣顯然很耗費(fèi)性能。

所以偏向鎖的作用就是,線程在沒(méi)有線程競(jìng)爭(zhēng)的情況下去訪問(wèn)synchronized同步代碼塊時(shí),會(huì)嘗試先通過(guò)偏向鎖來(lái)?yè)屨荚L問(wèn)資格,這個(gè)搶占過(guò)程是基于CAS來(lái)完成的,如果搶占鎖成功,則直接修改對(duì)象頭中的鎖標(biāo)記。其中,偏向鎖標(biāo)記為1,鎖標(biāo)記為01,以及存儲(chǔ)當(dāng)前獲得鎖的線程ID。而偏向的意思就是,如果線程X獲得了偏向鎖,那么當(dāng)線程X后續(xù)再訪問(wèn)這個(gè)同步方法時(shí),只需要判斷對(duì)象頭中的線程ID和線程X是否相等即

獲取偏向鎖的流程

下圖代表獲取偏向鎖的粗粒度流程圖,偏向鎖是在沒(méi)有線程競(jìng)爭(zhēng)的情況下實(shí)現(xiàn)的一種鎖,不能排除存在鎖競(jìng)爭(zhēng)的情況,所以偏向鎖的獲取有兩種情況。

  • 沒(méi)有鎖競(jìng)爭(zhēng)

    在沒(méi)有鎖競(jìng)爭(zhēng)并且開(kāi)啟了偏向鎖的情況下,當(dāng)線程1訪問(wèn)synchronized(lock)修飾的代碼塊時(shí):

    • 從當(dāng)前線程的棧中找到一個(gè)空閑的BasicObjectLock(圖中Lock Record),它是一個(gè)基礎(chǔ)的鎖對(duì)象,在后續(xù)的輕量級(jí)鎖和重量級(jí)鎖中都會(huì)用到,BasicObjectLock包含以下兩個(gè)屬性。
      • BasicLock,該屬性中有一個(gè)字段markOop,用于保存指向lock鎖對(duì)象的對(duì)象頭數(shù)據(jù)。
      • oop,指向lock鎖對(duì)象的指針。
    • 將BasicObjectLock中的oop指針指向當(dāng)前的鎖對(duì)象lock。
    • 獲得當(dāng)前鎖對(duì)象lock的對(duì)象頭,通過(guò)對(duì)象頭來(lái)判斷是否可偏向,也就是說(shuō)鎖標(biāo)記為101,并且Thread Id為空。
      • 如果為可偏向狀態(tài),那么判斷當(dāng)前偏向的線程是不是線程1,如果偏向的是自己,則不需要再搶占鎖,直接有資格運(yùn)行同步代碼塊。
      • 如果為不可偏向狀態(tài),則需要通過(guò)輕量級(jí)鎖來(lái)完成鎖的搶占過(guò)程。
    • 如果對(duì)象鎖lock偏向其他線程或者當(dāng)前是匿名偏向狀態(tài)(也就是沒(méi)有偏向任何一個(gè)線程),則先構(gòu)建一個(gè)匿名偏向的Mark Word,然后通過(guò)CAS方法,把一個(gè)匿名偏向的Mark Word修改為偏向線程1。如果當(dāng)前鎖對(duì)象lock已經(jīng)偏向了其他線程,那么CAS一定會(huì)失敗。
  • 存在鎖競(jìng)爭(zhēng)

    假設(shè)線程1獲得了偏向鎖,此時(shí)線程2去執(zhí)行synchronized(lock)同步代碼塊,如果訪問(wèn)到同一個(gè)對(duì)象鎖則會(huì)觸發(fā)鎖競(jìng)爭(zhēng)并觸發(fā)偏向鎖撤銷(xiāo),撤銷(xiāo)流程如下。

    • 線程2調(diào)用撤銷(xiāo)偏向鎖方法,嘗試撤銷(xiāo)lock鎖對(duì)象的偏向鎖。
    • 撤銷(xiāo)偏向鎖需要到達(dá)全局安全點(diǎn)(SafePoint)才會(huì)執(zhí)行,全局安全點(diǎn)就是當(dāng)前線程運(yùn)行到的這個(gè)位置,線程的狀態(tài)可以被確定,堆對(duì)象的狀態(tài)也是確定的,在這個(gè)位置JVM可以安全地進(jìn)行GC、偏向鎖撤銷(xiāo)等動(dòng)作。當(dāng)?shù)竭_(dá)全局安全點(diǎn)后,會(huì)暫停獲得偏向鎖的線程1。
    • 檢查獲得偏向鎖的線程1的狀態(tài),這里存在兩種狀態(tài)。
      • 線程1已經(jīng)執(zhí)行完同步代碼塊或者處于非存活狀態(tài)。在這種情況下,直接把偏向鎖撤銷(xiāo)恢復(fù)成無(wú)鎖狀態(tài),然后線程2升級(jí)到輕量級(jí)鎖,通過(guò)輕量級(jí)鎖搶占鎖資源。
      • 線程1還在執(zhí)行同步代碼塊中的指令,也就是說(shuō)沒(méi)有退出同步代碼塊。在這種情況下,直接把鎖對(duì)象lock升級(jí)成輕量級(jí)鎖(由于這里是全局安全點(diǎn),所以不需要通過(guò)CAS來(lái)實(shí)現(xiàn)),并且指向線程1,表示線程1持有輕量級(jí)鎖,接著線程1繼續(xù)執(zhí)行同步代碼塊中的代碼。

偏向鎖的釋放

在偏向鎖執(zhí)行完synchronized同步代碼塊后,會(huì)觸發(fā)偏向鎖釋放的流程,需要注意的是,偏向鎖本質(zhì)上并沒(méi)有釋放,因?yàn)楫?dāng)前鎖對(duì)象lock仍然是偏向該線程的。釋放的過(guò)程只是把Lock Record釋放了,也就是說(shuō)把Lock Record保存的鎖對(duì)象的Mark Word設(shè)置為空。

偏向鎖批量重偏向當(dāng)一個(gè)鎖對(duì)象lock只被同一個(gè)線程訪問(wèn)時(shí),該鎖對(duì)象的鎖狀態(tài)就是偏向鎖,并且一直偏向該線程。當(dāng)有任何一個(gè)線程來(lái)訪問(wèn)該鎖對(duì)象lock時(shí),不管之前獲得偏向鎖線程的狀態(tài)是存活還是死亡,lock鎖對(duì)象都會(huì)升級(jí)為輕量級(jí)鎖,并且鎖在升級(jí)之后是不可逆的。

假設(shè)一個(gè)線程t1針對(duì)大量的鎖對(duì)象增加了偏向鎖,之后線程t2來(lái)訪問(wèn)這些鎖對(duì)象,在不考慮鎖競(jìng)爭(zhēng)的情況下,需要對(duì)之前所有偏向線程t1的鎖對(duì)象進(jìn)行偏向鎖撤銷(xiāo)和升級(jí),這個(gè)過(guò)程比較耗時(shí),而且虛擬機(jī)會(huì)認(rèn)為這個(gè)鎖不適合再偏向于原來(lái)的t1線程,于是當(dāng)偏向鎖撤銷(xiāo)次數(shù)達(dá)到20次時(shí),會(huì)觸發(fā)批量重偏向,把所有的鎖對(duì)象全部偏向線程t2。偏向鎖撤銷(xiāo)并批量重偏向的觸發(fā)閾值可以通過(guò)XX:BiasedLockingBulkRebiasThreshold = 20來(lái)配置,默認(rèn)是20。

在高并發(fā)場(chǎng)景下,當(dāng)大量線程同時(shí)競(jìng)爭(zhēng)同一個(gè)鎖資源時(shí),偏向鎖就會(huì)被撤銷(xiāo),發(fā)生 stop the word 后, 開(kāi)啟偏向鎖無(wú)疑會(huì)帶來(lái)更大的性能開(kāi)銷(xiāo),這時(shí)我們可以通過(guò)添加 JVM 參數(shù)關(guān)閉偏向鎖來(lái)調(diào)優(yōu)系統(tǒng)性能:

-XX:-UseBiasedLocking //關(guān)閉偏向鎖(默認(rèn)打開(kāi))
或者
-XX:+UseHeavyMonitors  //設(shè)置重量級(jí)鎖

輕量級(jí)鎖的原理

在線程沒(méi)有競(jìng)爭(zhēng)時(shí),使用偏向鎖能夠在不影響性能的前提下獲得鎖資源,但是同一時(shí)刻只允許一個(gè)線程獲得鎖資源,如果有多個(gè)線程來(lái)訪問(wèn)同步方法,于是就有了輕量級(jí)鎖的設(shè)計(jì)。

所謂的輕量級(jí)鎖,就是沒(méi)有搶占到鎖的線程,進(jìn)行一定次數(shù)的重試(CAS)。比如線程第一次沒(méi)搶到鎖則重試幾次,如果在重試的過(guò)程中搶占到了鎖,那么這個(gè)線程就不需要阻塞,這種實(shí)現(xiàn)方式我們稱(chēng)為自旋鎖,具體的實(shí)現(xiàn)流程如圖所示。

線程通過(guò)重試來(lái)?yè)屨兼i的方式是有代價(jià)的,因?yàn)榫€程如果不斷自旋重試,那么CPU會(huì)一直處于運(yùn)行狀態(tài)。如果持有鎖的線程占有鎖的時(shí)間比較短,那么自旋等待的實(shí)現(xiàn)帶來(lái)性能的提升會(huì)比較明顯。反之,如果持有鎖的線程占用鎖資源的時(shí)間比較長(zhǎng),那么自旋的線程就會(huì)浪費(fèi)CPU資源,所以線程重試搶占鎖的次數(shù)必須要有一個(gè)限制。從 JDK1.7 開(kāi)始,自旋鎖默認(rèn)啟用,自旋次數(shù)由 JVM 設(shè)置決定,根據(jù)前一次在同一個(gè)鎖上的自旋次數(shù)及鎖持有者的狀態(tài)來(lái)決定的。如果在同一個(gè)鎖對(duì)象上,通過(guò)自旋等待成功獲得過(guò)鎖,并且持有鎖的線程正在運(yùn)行中,那么JVM會(huì)認(rèn)為此次自旋也有很大的機(jī)會(huì)獲得鎖,因此會(huì)將這個(gè)線程的自旋時(shí)間相對(duì)延長(zhǎng)。反之,如果在一個(gè)鎖對(duì)象中,通過(guò)自旋鎖獲得鎖很少成功,那么JVM會(huì)縮短自旋次數(shù)。

在高負(fù)載、高并發(fā)的場(chǎng)景下,我們可以通過(guò)設(shè)置 JVM 參數(shù)來(lái)關(guān)閉自旋鎖,優(yōu)化系統(tǒng)性能:

-XX:-UseSpinning //參數(shù)關(guān)閉自旋鎖優(yōu)化(默認(rèn)打開(kāi)) 
-XX:PreBlockSpin //參數(shù)修改默認(rèn)的自旋次數(shù)。JDK1.7后,去掉此參數(shù),由jvm控制

如果偏向鎖存在競(jìng)爭(zhēng)或者偏向鎖未開(kāi)啟,那么當(dāng)線程訪問(wèn)synchronized(lock)同步代碼塊時(shí)就會(huì)采用輕量級(jí)鎖來(lái)?yè)屨兼i資源,獲得訪問(wèn)資格,輕量級(jí)鎖的加鎖如圖所示:

獲取輕量級(jí)鎖的流程

  • 在線程2進(jìn)入同步代碼塊后,JVM會(huì)給當(dāng)前線程分配一個(gè)Lock Record,也就是一個(gè)BasicObjectLock對(duì)象,在它的成員對(duì)象BasicLock中有一個(gè)成員屬性markOop _displaced_header,這個(gè)屬性專(zhuān)門(mén)用來(lái)保存鎖對(duì)象lock的原始Mark Word。

  • 構(gòu)建一個(gè)無(wú)鎖狀態(tài)的Mark Word(其實(shí)就是lock鎖對(duì)象的Mark Word,但是鎖狀態(tài)是無(wú)鎖),把這個(gè)無(wú)鎖狀態(tài)的Mark Word設(shè)置到Lock Record中的_displaced_header字段中,如圖所示:

  • 通過(guò)CAS將lock鎖對(duì)象的Mark Word替換為指向Lock Record的指針,如果替換成功,就會(huì)得到如圖所示的結(jié)構(gòu),表示輕量級(jí)鎖搶占成功,此時(shí)線程2可以執(zhí)行同步代碼塊。

  • 如果CAS失敗,則說(shuō)明當(dāng)前l(fā)ock鎖對(duì)象不是無(wú)鎖狀態(tài),會(huì)觸發(fā)鎖膨脹,升級(jí)到重量級(jí)鎖。

相對(duì)偏向鎖來(lái)說(shuō),輕量級(jí)鎖的原理比較簡(jiǎn)單,它只是通過(guò)CAS來(lái)修改鎖對(duì)象中指向Lock Record的指針。從功能層面來(lái)說(shuō),偏向鎖和輕量級(jí)鎖最大的不同是:

  • 偏向鎖只能保證偏向同一個(gè)線程,只要有線程獲得過(guò)偏向鎖,那么當(dāng)其他線程去搶占鎖時(shí),只能通過(guò)輕量級(jí)鎖來(lái)實(shí)現(xiàn),除非觸發(fā)了重新偏向(如果獲得輕量級(jí)鎖的線程在后續(xù)的20次訪問(wèn)中,發(fā)現(xiàn)每次訪問(wèn)鎖的線程都是同一個(gè),則會(huì)觸發(fā)重新偏向,20次的定義屬性為:XX:BiasedLockingBulkRebiasThreshold =20)。
  • 輕量級(jí)鎖可以靈活釋放,也就是說(shuō),如果線程1搶占了輕量級(jí)鎖,那么在鎖用完并釋放后,線程2可以繼續(xù)通過(guò)輕量級(jí)鎖來(lái)?yè)屨兼i資源。

輕量級(jí)鎖的釋放

偏向鎖也有鎖釋放的邏輯,但是它只是釋放Lock Record,原本的偏向關(guān)系仍然存在,所以并不是真正意義上的鎖釋放。而輕量級(jí)鎖釋放之后,其他線程可以繼續(xù)使用輕量級(jí)鎖來(lái)?yè)屨兼i資源,具體的實(shí)現(xiàn)流程如下。

  • 把Lock Record中_displaced_header存儲(chǔ)的lock鎖對(duì)象的Mark Word替換到lock鎖對(duì)象的Mark Word中,這個(gè)過(guò)程會(huì)采用CAS來(lái)完成。
  • 如果CAS成功,則輕量級(jí)鎖釋放完成。
  • 如果CAS失敗,說(shuō)明釋放鎖的時(shí)候發(fā)生了競(jìng)爭(zhēng),就會(huì)觸發(fā)鎖膨脹,完成鎖膨脹之后,再調(diào)用重量級(jí)鎖的釋放鎖方法,完成鎖的釋放過(guò)程。

重量級(jí)鎖的原理分析

輕量級(jí)鎖能夠通過(guò)一定次數(shù)的重試讓沒(méi)有獲得鎖的線程有可能搶占到鎖資源,但是輕量級(jí)鎖只有在獲得鎖的線程持有鎖的時(shí)間較短的情況下才能起到提升同步鎖性能的效果。如果持有鎖的線程占用鎖資源的時(shí)間較長(zhǎng),那么不能讓那些沒(méi)有搶占到鎖資源的線程不斷自旋,否則會(huì)占用過(guò)多的CPU資源,這反而是一件得不償失的事情。如果沒(méi)搶占到鎖資源的線程通過(guò)一定次數(shù)的自旋后,發(fā)現(xiàn)仍然沒(méi)有獲得鎖,就只能阻塞等待了,所以最終會(huì)升級(jí)到重量級(jí)鎖,通過(guò)系統(tǒng)層面的互斥量(Mutex)來(lái)?yè)屨兼i資源。重量級(jí)鎖的實(shí)現(xiàn)原理如圖所示:

如果線程在運(yùn)行synchronized(lock)同步代碼塊時(shí),發(fā)現(xiàn)鎖狀態(tài)是輕量級(jí)鎖并且有其他線程搶占了鎖資源,那么該線程就會(huì)觸發(fā)鎖膨脹升級(jí)到重量級(jí)鎖。因此,重量級(jí)鎖是在存在線程競(jìng)爭(zhēng)的場(chǎng)景中使用的鎖類(lèi)型。

獲取重量級(jí)鎖的流程

重量級(jí)鎖的實(shí)現(xiàn)流程如圖所示:

重量級(jí)鎖的實(shí)現(xiàn)是在ObjectMonitor中完成的,所以鎖膨脹的意義就是構(gòu)建一個(gè)ObjectMonitor,繼續(xù)關(guān)注圖中ObjectMonitor的實(shí)現(xiàn)部分,在ObjectMonitor中鎖的實(shí)現(xiàn)過(guò)程如下:

  • 首先,判斷當(dāng)前線程是否是重入,如果是則增加重入次數(shù)。
  • 然后,通過(guò)自旋鎖來(lái)實(shí)現(xiàn)鎖的搶占(這個(gè)自旋鎖就是前面我們提到的自適應(yīng)自旋),這里使用CAS機(jī)制來(lái)判斷ObjectMonitor中的_owner字段是否為空,如果為空就表示重量級(jí)鎖已釋放,當(dāng)前線程可以獲得鎖,否則就進(jìn)行自適應(yīng)自旋重試。
  • 最后,如果通過(guò)自旋鎖競(jìng)爭(zhēng)鎖失敗,則會(huì)把當(dāng)前線程構(gòu)建成一個(gè)ObjectWaiter節(jié)點(diǎn),插入_cxq隊(duì)列的隊(duì)首,再使用park方法阻塞當(dāng)前線程。

重量級(jí)鎖的釋放

鎖的釋放是在synchronized同步代碼塊結(jié)束后觸發(fā)的,釋放的邏輯比較簡(jiǎn)單。

  • 把ObjectMonitor中持有鎖的對(duì)象_owner置為null。
  • 從_cxq隊(duì)列中喚醒一個(gè)處于鎖阻塞的線程。
  • 被喚醒的線程會(huì)重新競(jìng)爭(zhēng)重量級(jí)鎖,需要注意的是,synchronized是非公平鎖,因此被喚醒后不一定能夠搶占到鎖,如果沒(méi)搶到,則繼續(xù)等待。

總結(jié)

JVM 在 JDK1.6 中引入了分級(jí)鎖機(jī)制來(lái)優(yōu)化 Synchronized,當(dāng)一個(gè)線程獲取鎖時(shí),首先對(duì)象鎖將成為一個(gè)偏向鎖,這樣做是為了優(yōu)化同一線程重復(fù)獲取導(dǎo)致的用戶(hù)態(tài)與內(nèi)核態(tài)的切換問(wèn)題;其次如果有多個(gè)線程競(jìng)爭(zhēng)鎖資源,鎖將會(huì)升級(jí)為輕量級(jí)鎖,它適用于在短時(shí)間內(nèi)持有鎖,且分鎖有交替切換的場(chǎng)景;輕量級(jí)鎖還使用了自旋鎖來(lái)避免線程用戶(hù)態(tài)與內(nèi)核態(tài)的頻繁切換,大大地提高了系統(tǒng)性能;但如果鎖競(jìng)爭(zhēng)太激烈了,那么同步鎖將會(huì)升級(jí)為重量級(jí)鎖。減少鎖競(jìng)爭(zhēng),是優(yōu)化 Synchronized 同步鎖的關(guān)鍵。我們應(yīng)該盡量使 Synchronized 同步鎖處于輕量級(jí)鎖或偏向鎖,這樣才能提高 Synchronized 同步鎖的性能;通過(guò)減小鎖粒度來(lái)降低鎖競(jìng)爭(zhēng)也是一種最常用的優(yōu)化方法;另外我們還可以通過(guò)減少鎖的持有時(shí)間來(lái)提高 Synchronized 同步鎖在自旋時(shí)獲取鎖資源的成功率,避免 Synchronized 同步鎖升級(jí)為重量級(jí)鎖。

以上就是深入剖析Java中的synchronized關(guān)鍵字的詳細(xì)內(nèi)容,更多關(guān)于Java synchronized關(guān)鍵字的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • SpringBoot自動(dòng)裝配原理詳解

    SpringBoot自動(dòng)裝配原理詳解

    這篇文章主要介紹了SpringBoot自動(dòng)裝配原理的相關(guān)資料,幫助大家更好的理解和學(xué)習(xí)使用SpringBoot框架,感興趣的朋友可以了解下
    2021-03-03
  • 詳解mybatis通過(guò)mapper接口加載映射文件

    詳解mybatis通過(guò)mapper接口加載映射文件

    本篇文章主要介紹了mybatis通過(guò)mapper接口加載映射文件 ,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-08-08
  • Java中的隨機(jī)數(shù)詳解

    Java中的隨機(jī)數(shù)詳解

    這篇文章主要介紹了Java中的隨機(jī)數(shù),需要的朋友可以參考下
    2014-02-02
  • Java新手環(huán)境搭建 Tomcat安裝配置教程

    Java新手環(huán)境搭建 Tomcat安裝配置教程

    這篇文章主要為大家詳細(xì)介紹了Java新手環(huán)境搭建的相關(guān)資料,Tomcat安裝配置教程,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-03-03
  • Maven腳手架如何基于jeecg實(shí)現(xiàn)快速開(kāi)發(fā)

    Maven腳手架如何基于jeecg實(shí)現(xiàn)快速開(kāi)發(fā)

    這篇文章主要介紹了Maven腳手架如何基于jeecg實(shí)現(xiàn)快速開(kāi)發(fā),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-10-10
  • Java中callable的實(shí)現(xiàn)原理

    Java中callable的實(shí)現(xiàn)原理

    本文主要介紹了Java里的callable的實(shí)現(xiàn)原理,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2024-03-03
  • SpringCloud openfeign相互調(diào)用實(shí)現(xiàn)方法介紹

    SpringCloud openfeign相互調(diào)用實(shí)現(xiàn)方法介紹

    在springcloud中,openfeign是取代了feign作為負(fù)載均衡組件的,feign最早是netflix提供的,他是一個(gè)輕量級(jí)的支持RESTful的http服務(wù)調(diào)用框架,內(nèi)置了ribbon,而ribbon可以提供負(fù)載均衡機(jī)制,因此feign可以作為一個(gè)負(fù)載均衡的遠(yuǎn)程服務(wù)調(diào)用框架使用
    2022-11-11
  • java實(shí)現(xiàn)學(xué)生成績(jī)錄入系統(tǒng)

    java實(shí)現(xiàn)學(xué)生成績(jī)錄入系統(tǒng)

    這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)學(xué)生成績(jī)錄入系統(tǒng),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-01-01
  • IDEA之如何關(guān)閉/開(kāi)啟引用提示Usages

    IDEA之如何關(guān)閉/開(kāi)啟引用提示Usages

    這篇文章主要介紹了IDEA之如何關(guān)閉/開(kāi)啟引用提示Usages問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-07-07
  • 流式圖表拒絕增刪改查之框架搭建過(guò)程

    流式圖表拒絕增刪改查之框架搭建過(guò)程

    這篇文章主要為大家介紹了流式圖表拒絕增刪改查之框架搭建過(guò)程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-04-04

最新評(píng)論