<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          17張圖帶你秒殺Synchronized關(guān)鍵字

          共 8705字,需瀏覽 18分鐘

           ·

          2021-07-29 10:02

          寫(xiě)在前面

          最近我和我在BAT的小伙伴合作,做了一個(gè)互聯(lián)網(wǎng)大廠八股文在線題庫(kù),這個(gè)網(wǎng)站定期更新最近各大廠面試題,歡迎大家關(guān)注。


          網(wǎng)站鏈接:http://interviewtop.top


          d2e84c04bd8605c9e65c163a9ab0d466.webp


          引子

          小艾和小牛在路上相遇,小艾一臉沮喪。

          小牛:小艾小艾,發(fā)生甚么事了?

          小艾:別提了,昨天有個(gè)面試官問(wèn)了我好幾個(gè)關(guān)于 synchronized 關(guān)鍵字的問(wèn)題,沒(méi)答上來(lái)。

          小艾:我后來(lái)查了很多資料,有二十多頁(yè)的概念說(shuō)明,也有三十來(lái)頁(yè)的源碼剖析,看得我頭大。

          小牛:你那看的是死知識(shí),不好用,你得聽(tīng)我的總結(jié)。

          小艾:看來(lái)是有備而來(lái),那您給講講吧。

          小牛:那咱們開(kāi)始!

          synchronized關(guān)鍵字引入

          我們知道,在多線程程序中往往會(huì)出現(xiàn)這么一個(gè)情況:多個(gè)線程同時(shí)訪問(wèn)某個(gè)線程間的共享變量。來(lái)舉個(gè)例子吧:

          假設(shè)銀行存款業(yè)務(wù)寫(xiě)了兩個(gè)方法,一個(gè)是存錢(qián) store() 方法 ,一個(gè)是查詢(xún)余額 get() 方法。假設(shè)初始客戶(hù)小明的賬戶(hù)余額為 0 元。(PS:這個(gè)例子只是個(gè) toy demo,為了方便大家理解寫(xiě)的,真實(shí)的業(yè)務(wù)場(chǎng)景不會(huì)這樣。)

          ????//?account?客戶(hù)在銀行的存款?
          ????public?void?store(int?money){
          ????????int?newAccount=account+money;
          ????????account=newAccount;
          ????}
          ????public?void?get(){
          ????????System.out.print("小明的銀行賬戶(hù)余額:");
          ????????System.out.print(account);
          ????}

          如果小明為自己存款 1 元,我們期望的線程調(diào)用情況如下:

          1. 首先會(huì)啟動(dòng)一個(gè)線程調(diào)用 store() 方法,為客戶(hù)賬戶(hù)余額增加 1;
          2. 再啟動(dòng)一個(gè)線程調(diào)用 get() 方法,輸出客戶(hù)的新余額為 1。

          但實(shí)際情況可能由于線程執(zhí)行的先后順序,出現(xiàn)如圖所示的錯(cuò)誤:

          5dc2426dc58a9f889bdd77667256a024.webp小明存錢(qián)流程6fcac64504e698cb87c2fe09198e93be.webp小明:咱家沒(méi)錢(qián)了

          小明會(huì)驚奇的以為自己的錢(qián)沒(méi)存上。這就是一個(gè)典型的由共享數(shù)據(jù)引發(fā)的并發(fā)數(shù)據(jù)沖突問(wèn)題

          解決方式也很簡(jiǎn)單,讓并發(fā)執(zhí)行會(huì)產(chǎn)生問(wèn)題的代碼段不并發(fā)行了。

          如果 store() 方法 執(zhí)行完,才能執(zhí)行 get() 方法,而不是像上圖一樣并發(fā)執(zhí)行,自然不會(huì)出現(xiàn)這個(gè)問(wèn)題。那如何才能做到呢?

          答案就是使用 synchronized 關(guān)鍵字。

          我們先從直覺(jué)上思考一下,如果要實(shí)現(xiàn)先執(zhí)行 store() 方法,再執(zhí)行 get() 方法的話該怎么設(shè)計(jì)。

          我們可以設(shè)置某個(gè)鎖,鎖會(huì)有兩種狀態(tài),分別是上鎖解鎖。在 store() 方法執(zhí)行之前,先觀察這個(gè)鎖的狀態(tài),如果是上鎖狀態(tài),就進(jìn)入阻塞,代碼不運(yùn)行;

          如果這把鎖是解鎖狀態(tài),那就先將這把鎖狀態(tài)變?yōu)樯湘i,之后接著運(yùn)行自己的代碼。運(yùn)行完成之后再將鎖狀態(tài)設(shè)置為解鎖。

          對(duì)于 get() 方法也是如此。

          Java 中的 synchronized 關(guān)鍵字就是基于這種思想設(shè)計(jì)的。在 synchronized 關(guān)鍵字中,鎖就是一個(gè)對(duì)象。

          synchronized 一共有三種使用方法:

          • 直接修飾某個(gè)實(shí)例方法。像上文代碼一樣,在這種情況下多線程并發(fā)訪問(wèn)實(shí)例方法時(shí),如果其他線程調(diào)用同一個(gè)對(duì)象的被 synchronized 修飾的方法,就會(huì)被阻塞。相當(dāng)于把鎖記錄在這個(gè)方法對(duì)應(yīng)的對(duì)象上。
          ????//?account?客戶(hù)在銀行的存款?
          ????public?synchronized?void?store(int?money){
          ????????int?newAccount=account+money;
          ????????account=newAccount;
          ????}
          ????public?synchronized?void?get(){
          ????????System.out.print("小明的銀行賬戶(hù)余額:");
          ????????System.out.print(account);
          ????}
          • 直接修飾某個(gè)靜態(tài)方法。在這種情況下進(jìn)行多線程并發(fā)訪問(wèn)時(shí),如果其他線程也是調(diào)用屬于同一類(lèi)的被 synchronized 修飾的靜態(tài)方法,就會(huì)被阻塞。相當(dāng)于把鎖信息記錄在這個(gè)方法對(duì)應(yīng)的類(lèi)上。
          ????public?synchronized?static?void?get(){
          ????????···
          ????}
          • 修飾代碼塊。如果此時(shí)有別的線程也想訪問(wèn)某個(gè)被synchronized(對(duì)象0)修飾的同步代碼塊時(shí),也會(huì)被阻塞。
          ????public?static?void?get(){
          ????????synchronized(對(duì)象0){
          ????????????···
          ????????}
          ????}

          小艾問(wèn):我看了不少參考書(shū)還有網(wǎng)上資料,都說(shuō) synchronized 的鎖是鎖在對(duì)象上的。關(guān)于這句話,你能深入講講嗎?

          小牛回答道:別急,我先講講 Java 對(duì)象在內(nèi)存中的表示。

          Java 對(duì)象在內(nèi)存中的表示

          講清 synchronized 關(guān)鍵字的原理前需要理清 Java 對(duì)象在內(nèi)存中的表示方法。

          0850460bff9f0b0b5f0d2091df102755.webpJava 對(duì)象在內(nèi)存中的表示

          上圖就是一個(gè) Java 對(duì)象在內(nèi)存中的表示。我們可以看到,內(nèi)存中的對(duì)象一般由三部分組成,分別是對(duì)象頭、對(duì)象實(shí)際數(shù)據(jù)和對(duì)齊填充。

          對(duì)象頭包含 Mark Word、Class Pointer和 Length 三部分。

          • Mark Word 記錄了對(duì)象關(guān)于鎖的信息,垃圾回收信息等。
          • Class Pointer 用于指向?qū)ο髮?duì)應(yīng)的 Class 對(duì)象(其對(duì)應(yīng)的元數(shù)據(jù)對(duì)象)的內(nèi)存地址。
          • Length只適用于對(duì)象是數(shù)組時(shí),它保存了該數(shù)組的長(zhǎng)度信息。

          對(duì)象實(shí)際數(shù)據(jù)包括了對(duì)象的所有成員變量,其大小由各個(gè)成員變量的大小決定。

          對(duì)齊填充表示最后一部分的填充字節(jié)位,這部分不包含有用信息。

          我們剛才講的鎖 synchronized 鎖使用的就是對(duì)象頭的 Mark Word 字段中的一部分。

          Mark Word 中的某些字段發(fā)生變化,就可以代表鎖不同的狀態(tài)。

          由于鎖的信息是記錄在對(duì)象里的,有的開(kāi)發(fā)者也往往會(huì)說(shuō)鎖住對(duì)象這種表述。

          無(wú)鎖狀態(tài)的 Mark Word

          這里我們以無(wú)鎖狀態(tài)的 Mark Word 字段舉例:

          如果當(dāng)前對(duì)象是無(wú)鎖狀態(tài),對(duì)象的 Mark Word 如圖所示。

          473e250b480cbc8715fe5e0d225c48db.webp無(wú)鎖狀態(tài)的 Mark Word 字段

          我們可以看到,該對(duì)象頭的 Mark Word 字段分為四個(gè)部分:

          1. 對(duì)象的 hashCode ;
          2. 對(duì)象的分代年齡,這部分用于對(duì)對(duì)象的垃圾回收;
          3. 是否為偏向鎖位,1代表是,0代表不是;
          4. 鎖標(biāo)志位,這里是 01。

          synchronized關(guān)鍵字的實(shí)現(xiàn)原理

          講完了 Java 對(duì)象在內(nèi)存中的表示,我們下一步來(lái)講講 synchronized 關(guān)鍵字的實(shí)現(xiàn)原理。

          從前文中我們可以看到, synchronized 關(guān)鍵字有兩種修飾方法

          1. 直接作為關(guān)鍵字修飾在方法上,將整個(gè)方法作為同步代碼塊:
          ????public?synchronized?static?void?`get()`{
          ????????···
          ????}
          1. 修飾在同步代碼塊上
          ????public?static?void?`get()`{
          ????????synchronized(對(duì)象0){
          ????????????···
          ????????}
          ????}

          針對(duì)這兩種情況,Java 編譯時(shí)的處理方法并不相同。

          對(duì)于第一種情況,編譯器會(huì)為其自動(dòng)生成了一個(gè) ACC_SYNCHRONIZED 關(guān)鍵字用來(lái)標(biāo)識(shí)。

          在 JVM 進(jìn)行方法調(diào)用時(shí),當(dāng)發(fā)現(xiàn)調(diào)用的方法被 ACC_SYNCHRONIZED 修飾,則會(huì)先嘗試獲得鎖。

          對(duì)于第二種情況,編譯時(shí)在代碼塊開(kāi)始前生成對(duì)應(yīng)的1個(gè) monitorenter 指令,代表同步塊進(jìn)入。2個(gè) monitorexit 指令,代表同步塊退出。

          這兩種方法底層都需要一個(gè) reference 類(lèi)型的參數(shù),指明要鎖定和解鎖的對(duì)象。

          如果 synchronized 明確指定了對(duì)象參數(shù),那就是該對(duì)象。

          如果沒(méi)有明確指定,那就根據(jù)修飾的方法是實(shí)例方法還是類(lèi)方法,取對(duì)應(yīng)的對(duì)象實(shí)例或類(lèi)對(duì)象(Java 中類(lèi)也是一種特殊的對(duì)象)作為鎖對(duì)象。

          f88782da3871f9963a295dbb4f9e0c5c.webp確定鎖定和解鎖的對(duì)象

          每個(gè)對(duì)象維護(hù)著一個(gè)記錄著被鎖次數(shù)的計(jì)數(shù)器。當(dāng)一個(gè)線程執(zhí)行 monitorenter,該計(jì)數(shù)器自增從 0 變?yōu)?1;

          當(dāng)一個(gè)線程執(zhí)行 monitorexit,計(jì)數(shù)器再自減。當(dāng)計(jì)數(shù)器為 0 的時(shí)候,說(shuō)明對(duì)象的鎖已經(jīng)釋放。

          小艾問(wèn):為什么會(huì)有兩個(gè) monitorexit 指令呢?

          小牛答:正常退出,得用一個(gè) monitorexit 吧,如果中間出現(xiàn)異常,鎖會(huì)一直無(wú)法釋放。所以編譯器會(huì)為同步代碼塊添加了一個(gè)隱式的 try-finally 異常處理,在 finally 中會(huì)調(diào)用 monitorexit 命令最終釋放鎖。

          重量級(jí)鎖

          小艾問(wèn):那么問(wèn)題來(lái)了,之前你說(shuō)鎖的信息是記錄在對(duì)象的 Mark Word 中的,那現(xiàn)在冒出來(lái)的 monitor 又是什么呢?

          小牛答:我們先來(lái)看一下重量級(jí)鎖對(duì)應(yīng)對(duì)象的 Mark Word。

          在 Java 的早期版本中,synchronized 鎖屬于重量級(jí)鎖,此時(shí)對(duì)象的 Mark Word 如圖所示。

          732df0129e8cdae2ce580a6db5ce799d.webp重量級(jí)鎖的 Mark Word 字段

          我們可以看到,該對(duì)象頭的 Mark Word 分為兩個(gè)部分。第一部分是指向重量級(jí)鎖的指針,第二部分是鎖標(biāo)記位。

          而這里所說(shuō)的指向重量級(jí)鎖的指針就是 monitor

          英文詞典翻譯 monitor 是監(jiān)視器。Java 中每個(gè)對(duì)象會(huì)對(duì)應(yīng)一個(gè)監(jiān)視器。

          這個(gè)監(jiān)視器其實(shí)也就是監(jiān)控鎖有沒(méi)有釋放,釋放的話會(huì)通知下一個(gè)等待鎖的線程去獲取。

          monitor 的成員變量比較多,我們可以這樣理解:

          2b03c724dd7b50aa35004d8f32d723ac.webpmonitor結(jié)構(gòu)

          我們可以將 monitor 簡(jiǎn)單理解成兩部分,第一部分表示當(dāng)前占用鎖的線程,第二部分是等待這把鎖的線程隊(duì)列

          如果當(dāng)前占用鎖的線程把鎖釋放了,那就需要在線程隊(duì)列中喚醒下一個(gè)等待鎖的線程。

          但是阻塞或喚醒一個(gè)線程需要依賴(lài)底層的操作系統(tǒng)來(lái)實(shí)現(xiàn),Java 的線程是映射到操作系統(tǒng)的原生線程之上的。

          而操作系統(tǒng)實(shí)現(xiàn)線程之間的切換需要從用戶(hù)態(tài)轉(zhuǎn)換到核心態(tài),這個(gè)狀態(tài)轉(zhuǎn)換需要花費(fèi)很多的處理器時(shí)間,甚至可能比用戶(hù)代碼執(zhí)行的時(shí)間還要長(zhǎng)。

          由于這種效率太低,Java 后期做了改進(jìn),我再來(lái)詳細(xì)講一講。

          CAS算法

          在講其他改進(jìn)之前,我們先來(lái)聊聊 CAS 算法。CAS 算法全稱(chēng)為 Compare And Swap。

          顧名思義,該算法涉及到了兩個(gè)操作,比較(Compare)和交換(Swap)。

          怎么理解這個(gè)操作呢?我們來(lái)看下圖:

          fce7695cd2c3445b1807f8fbeb0fea3b.webpCAS 算法

          我們知道,在對(duì)共享變量進(jìn)行多線程操作的時(shí)候,難免會(huì)出現(xiàn)線程安全問(wèn)題。

          對(duì)該問(wèn)題的一種解決策略就是對(duì)該變量加鎖,保證該變量在某個(gè)時(shí)間段只能被一個(gè)線程操作。

          但是這種方式的系統(tǒng)開(kāi)銷(xiāo)比較大。因此開(kāi)發(fā)人員提出了一種新的算法,就是大名鼎鼎的 CAS 算法。

          CAS 算法的思路如下:

          1. 該算法認(rèn)為線程之間對(duì)變量的操作進(jìn)行競(jìng)爭(zhēng)的情況比較少。
          2. 算法的核心是對(duì)當(dāng)前讀取變量值 E 和內(nèi)存中的變量舊值 V 進(jìn)行比較。
          3. 如果相等,就代表其他線程沒(méi)有對(duì)該變量進(jìn)行修改,就將變量值更新為新值 N
          4. 如果不等,就認(rèn)為在讀取值 E 到比較階段,有其他線程對(duì)變量進(jìn)行過(guò)修改,不進(jìn)行任何操作。

          當(dāng)線程運(yùn)行 CAS 算法時(shí),該運(yùn)行過(guò)程是原子操作,原子操作的含義就是線程開(kāi)始跑這個(gè)函數(shù)后,運(yùn)行過(guò)程中不會(huì)被別的程序打斷。

          我們來(lái)看看實(shí)際上 Java 語(yǔ)言中如何使用這個(gè) CAS 算法,這里我們以 AtomicInteger 類(lèi)中的 compareAndSwapInt() 方法舉例:

          public?final?native?boolean?compareAndSwapInt
          (Object?var1,?long?var2,?int?var3,?int?var4)

          可以看到,該函數(shù)原型接受四個(gè)參數(shù):

          1. 第一個(gè)參數(shù)是一個(gè) AtomicInteger 對(duì)象。
          2. 第二個(gè)參數(shù)是該 AtomicInteger 對(duì)象對(duì)應(yīng)的成員變量在內(nèi)存中的地址。
          3. 第三個(gè)參數(shù)是上圖中說(shuō)的線程之前讀取的值 P
          4. 第四個(gè)參數(shù)是上圖中說(shuō)的線程計(jì)算的新值 V

          偏向鎖

          JDK 1.6 中提出了偏向鎖的概念。該鎖提出的原因是,開(kāi)發(fā)者發(fā)現(xiàn)多數(shù)情況下鎖并不存在競(jìng)爭(zhēng),一把鎖往往是由同一個(gè)線程獲得的。

          如果是這種情況,不斷的加鎖解鎖是沒(méi)有必要的。

          那么能不能讓 JVM 直接負(fù)責(zé)在這種情況下加解鎖的事情,不讓操作系統(tǒng)插手呢?

          因此開(kāi)發(fā)者設(shè)計(jì)了偏向鎖。偏向鎖在獲取資源的時(shí)候,會(huì)在資源對(duì)象上記錄該對(duì)象是否偏向該線程。

          偏向鎖并不會(huì)主動(dòng)釋放,這樣每次偏向鎖進(jìn)入的時(shí)候都會(huì)判斷該資源是否是偏向自己的,如果是偏向自己的則不需要進(jìn)行額外的操作,直接可以進(jìn)入同步操作。

          下圖表示偏向鎖的 Mark Word結(jié)構(gòu):

          85322c161be9e3eb0a5e7b388bcd7f53.webp偏向鎖的 Mark Word 字段

          可以看到,偏向鎖對(duì)應(yīng)的 Mark Word 包含該偏向鎖對(duì)應(yīng)的線程 ID、偏向鎖的時(shí)間戳和對(duì)象分代年齡。

          偏向鎖的申請(qǐng)流程

          我們?cè)賮?lái)看一下偏向鎖的申請(qǐng)流程:

          1. 首先需要判斷對(duì)象的 Mark Word 是否屬于偏向模式,如果不屬于,那就進(jìn)入輕量級(jí)鎖判斷邏輯。否則繼續(xù)下一步判斷;
          2. 判斷目前請(qǐng)求鎖的線程 ID 是否和偏向鎖本身記錄的線程 ID 一致。如果一致,繼續(xù)下一步的判斷,如果不一致,跳轉(zhuǎn)到步驟4;
          3. 判斷是否需要重偏向,重偏向邏輯在后面一節(jié)批量重偏向和批量撤銷(xiāo)會(huì)說(shuō)明。如果不用的話,直接獲得偏向鎖;
          4. 利用 CAS 算法將對(duì)象的 Mark Word 進(jìn)行更改,使線程 ID 部分換成本線程 ID。如果更換成功,則重偏向完成,獲得偏向鎖。如果失敗,則說(shuō)明有多線程競(jìng)爭(zhēng),升級(jí)為輕量級(jí)鎖。
          690f4d2a65279f292535bff0543f8348.webp偏向鎖的申請(qǐng)流程

          值得注意的是,在執(zhí)行完同步代碼后,線程不會(huì)主動(dòng)去修改對(duì)象的 Mark Word,讓它重回?zé)o鎖狀態(tài)。

          所以一般執(zhí)行完 synchronized 語(yǔ)句后,如果是偏向鎖的狀態(tài)的話,線程對(duì)鎖的釋放操作可能是什么都不做。

          匿名偏向鎖

          在 JVM 開(kāi)啟偏向鎖模式下,如果一個(gè)對(duì)象被新建,在四秒后,該對(duì)象的對(duì)象頭就會(huì)被置為偏向鎖。

          一般來(lái)說(shuō),當(dāng)一個(gè)線程獲取了一把偏向鎖時(shí),會(huì)在對(duì)象頭和棧幀中的鎖記錄里不僅說(shuō)明目前是偏向鎖狀態(tài),也會(huì)存儲(chǔ)鎖偏向的線程 ID。

          在 JVM 四秒自動(dòng)創(chuàng)建偏向鎖的情況下,線程 ID 為0。

          由于這種情況下的偏向鎖不是由某個(gè)線程求得生成的,這種情況下的偏向鎖也稱(chēng)為匿名偏向鎖。

          批量重偏向和批量撤銷(xiāo)

          生產(chǎn)者消費(fèi)者模式下,生產(chǎn)者線程負(fù)責(zé)對(duì)象的創(chuàng)建,消費(fèi)者線程負(fù)責(zé)對(duì)生產(chǎn)出來(lái)的對(duì)象進(jìn)行使用。

          當(dāng)生產(chǎn)者線程創(chuàng)建了大量對(duì)象并執(zhí)行加偏向鎖的同步操作,消費(fèi)者對(duì)對(duì)象使用之后,會(huì)產(chǎn)生大量偏向鎖執(zhí)行和偏向鎖撤銷(xiāo)的問(wèn)題。

          d4fdb3dd6d2392b2c09cd732f5615653.webp大量偏向鎖執(zhí)行和偏向鎖撤銷(xiāo)的問(wèn)題

          Russell K和 Detlefs D在他們的文章提出了批量重偏向和批量撤銷(xiāo)的過(guò)程。

          在上圖情景下,他們探討了能不能直接將偏向的線程換成消費(fèi)者的線程。

          替換不是一件容易事,需要在 JVM 的眾多線程中找到類(lèi)似上文情景的線程。

          他們最后提出的解決方法是:

          以類(lèi)為單位,為每個(gè)類(lèi)維護(hù)一個(gè)偏向鎖撤銷(xiāo)計(jì)數(shù)器,每一次該類(lèi)的對(duì)象發(fā)生偏向撤銷(xiāo)操作時(shí),該計(jì)數(shù)器計(jì)數(shù) +1,當(dāng)這個(gè)計(jì)數(shù)值達(dá)到重偏向閾值時(shí),JVM 就認(rèn)為該類(lèi)可能不適合正常邏輯,適合批量重偏向邏輯。這就是對(duì)應(yīng)上圖流程圖里的是否需要重偏向過(guò)程。

          以生產(chǎn)者消費(fèi)者為例,生產(chǎn)者生產(chǎn)同一類(lèi)型的對(duì)象給消費(fèi)者,然后消費(fèi)者對(duì)這些對(duì)象都需要執(zhí)行偏向鎖撤銷(xiāo),當(dāng)撤銷(xiāo)過(guò)程過(guò)多時(shí)就會(huì)觸發(fā)上文規(guī)則,JVM 就注意到這個(gè)類(lèi)了。

          5e5575708a4441b3e22f1a426e7fea9f.webp批量重偏向和批量撤銷(xiāo)

          具體規(guī)則是:

          1. 每個(gè)類(lèi)對(duì)象會(huì)有一個(gè)對(duì)應(yīng)的 epoch 字段,每個(gè)處于偏向鎖狀態(tài)對(duì)象的 Mark Word 中也有該字段,其初始值為創(chuàng)建該對(duì)象時(shí),類(lèi)對(duì)象中的 epoch 的值。
          2. 每次發(fā)生批量重偏向時(shí),就將類(lèi)對(duì)象的 epoch 字段 +1,得到新的值 epoch_new
          3. 遍歷 JVM 中所有線程的棧,找到該類(lèi)對(duì)象,將其 epoch 字段改為新值。根據(jù)線程棧的信息判斷出該線程是否鎖定了該對(duì)象,將現(xiàn)在偏向鎖還在被使用的對(duì)象賦新值 epoch_new
          4. 下次有線程想獲得鎖時(shí),如果發(fā)現(xiàn)當(dāng)前對(duì)象的 epoch 值和類(lèi)的 epoch 不相等,不會(huì)執(zhí)行撤銷(xiāo)操作,而是直接通過(guò) CAS 操作將其 Mark Word 的 Thread ID 改成當(dāng)前線程 ID。

          批量撤銷(xiāo)相對(duì)于批量重偏向好理解得多,JVM 也會(huì)統(tǒng)計(jì)重偏向的次數(shù)。

          假設(shè)該類(lèi)計(jì)數(shù)器計(jì)數(shù)繼續(xù)增加,當(dāng)其達(dá)到批量撤銷(xiāo)的閾值后(默認(rèn)40),JVM 就認(rèn)為該類(lèi)的使用場(chǎng)景存在多線程競(jìng)爭(zhēng),會(huì)標(biāo)記該類(lèi)為不可偏向,之后對(duì)于該類(lèi)的鎖升級(jí)為輕量級(jí)鎖。

          輕量級(jí)鎖

          輕量級(jí)鎖的設(shè)計(jì)初衷在于并發(fā)程序開(kāi)發(fā)者的經(jīng)驗(yàn)“對(duì)于絕大部分的鎖,在整個(gè)同步周期內(nèi)都是不存在競(jìng)爭(zhēng)的”。

          所以它的設(shè)計(jì)出發(fā)點(diǎn)也在線程競(jìng)爭(zhēng)情況較少的情況下。我們先來(lái)看一下輕量級(jí)鎖的 Mark Word 布局。

          如果當(dāng)前對(duì)象是輕量級(jí)鎖狀態(tài),對(duì)象的 Mark Word 如下圖所示。

          f0f7d312a918269e649f58f4a7259dc9.webp輕量級(jí)鎖 Mark Word 字段

          我們可以看到,該對(duì)象頭Mark Word分為兩個(gè)部分。第一部分是指向棧中的鎖記錄的指針,第二部分是鎖標(biāo)記位,針對(duì)輕量級(jí)鎖該標(biāo)記位為 00。

          小艾問(wèn):那這指向棧中的鎖記錄的指針是什么意思呢?

          小牛答:這得結(jié)合輕量級(jí)鎖的上鎖步驟來(lái)慢慢講。

          如果當(dāng)前這個(gè)對(duì)象的鎖標(biāo)志位為 01(即無(wú)鎖狀態(tài)或者輕量級(jí)鎖狀態(tài)),線程在執(zhí)行同步塊之前,JVM 會(huì)先在當(dāng)前的線程的棧幀中創(chuàng)建一個(gè) Lock Record,包括一個(gè)用于存儲(chǔ)對(duì)象頭中的 Mark Word 以及一個(gè)指向?qū)ο蟮闹羔槨?/p>85b5cba2b45a9f3505fa43f2fe003753.webpLock Record

          然后 JVM 會(huì)利用 CAS 算法對(duì)這個(gè)對(duì)象的 Mark Word 進(jìn)行修改。如果修改成功,那該線程就擁有了這個(gè)對(duì)象的鎖。我們來(lái)看一下如果上圖的線程執(zhí)行 CAS 算法成功的結(jié)果。

          808b370fd1ffe2d3599e9b9493b84794.webp執(zhí)行 CAS 算法

          當(dāng)然 CAS 也會(huì)有失敗的情況。如果 CAS 失敗,那就說(shuō)明同時(shí)執(zhí)行 CAS 操作的線程可不止一個(gè)了, Mark Word 也做了更改。

          首先虛擬機(jī)會(huì)檢查對(duì)象的 Mark Word 字段指向棧中的鎖記錄的指針是否指向當(dāng)前線程的棧幀。如果是,那就說(shuō)明可能出現(xiàn)了類(lèi)似 synchronized 中套 synchronized 情況:

          synchronized?(對(duì)象0)?{
          ????synchronized?(對(duì)象0)?{
          ????????···
          ????}
          }

          當(dāng)然這種情況下當(dāng)前線程已經(jīng)擁有這個(gè)對(duì)象的鎖,可以直接進(jìn)入同步代碼塊執(zhí)行。

          否則說(shuō)明鎖被其他線程搶占了,該鎖還需要升級(jí)為重量級(jí)鎖。

          和偏向鎖不同的是,執(zhí)行完同步代碼塊后,需要執(zhí)行輕量級(jí)鎖的解鎖過(guò)程。解鎖過(guò)程如下:

          1. 通過(guò) CAS 操作嘗試把線程棧幀中復(fù)制的 Mark Word 對(duì)象替換當(dāng)前對(duì)象的 Mark Word。

          2. 如果 CAS 算法成功,整個(gè)同步過(guò)程就完成了。

          3. 如果 CAS 算法失敗,則說(shuō)明存在競(jìng)爭(zhēng),鎖升級(jí)為重量級(jí)鎖。

          我們來(lái)總結(jié)一下輕量級(jí)鎖升級(jí)過(guò)程吧:

          a3dc73b48b409720b70969bb4d19413a.webp輕量級(jí)鎖的升級(jí)過(guò)程

          總結(jié)

          這次我們了解了 synchronized 底層實(shí)現(xiàn)原理和對(duì)應(yīng)的鎖升級(jí)過(guò)程。最后我們?cè)偻ㄟ^(guò)這張流程圖來(lái)回顧一下 synchronized 鎖升級(jí)過(guò)程吧。

          8c36040face1e2422cb9072676081d5b.webp鎖申請(qǐng)完整流程

          參考

          1. 實(shí)現(xiàn)Java虛擬機(jī):JVM故障診斷與性能優(yōu)化

          2. 深入理解java虛擬機(jī) JVM高級(jí)特性與最佳實(shí)踐

          3. Russell K , Detlefs D . Eliminating synchronization-related atomic operations with biased locking and bulk rebiasing[C]// Acm Sigplan Conference on Object-oriented Programming Systems. ACM, 2006.

          4. Dice D , Moir M S , Scherer Iii W N . Quickly reacquirable locks: US 2010.

          5. https://github.com/farmerjohngit/myblog/issues/12

          6. https://www.itqiankun.com/article/bias-lightweight-synchronized-lock

          7. https://www.itqiankun.com/article/bias-lock-epoch-effect

          8. https://www.hollischuang.com/archives/1883

          9. http://www.ideabuffer.cn/2017/05/06/Java%E5%AF%B9%E8%B1%A1%E5%86%85%E5%AD%98%E5%B8%83%E5%B1%80/

          10. http://www.ideabuffer.cn/2017/04/21/java-%E4%B8%AD%E7%9A%84%E9%94%81-%E5%81%8F%E5%90%91%E9%94%81%E3%80%81%E8%BD%BB%E9%87%8F%E7%BA%A7%E9%94%81%E3%80%81%E8%87%AA%E6%97%8B%E9%94%81%E3%80%81%E9%87%8D%E9%87%8F%E7%BA%A7%E9%94%81/

          11. https://blog.csdn.net/zhao_miao/article/details/84500771


          感謝各位少俠閱讀,我們將會(huì)為大家?guī)?lái)更多精彩原創(chuàng)文章

          點(diǎn)擊左下角【閱讀原文】參與留言討論

          推薦閱讀

          1. 26張圖帶你徹底了解volatile關(guān)鍵字
          2. 微軟程序員的算法學(xué)習(xí)之路
          771a504a6758464baeecab5fff5f5fcb.webp


          瀏覽 36
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  久久久免费三级片网站 | 欧美日韩亚洲成人论坛 | 日韩城人免费 | 你懂的在线视频 | aaa成人网站 |