當(dāng)前位置:首頁(yè) > 公眾號(hào)精選 > 架構(gòu)師社區(qū)
[導(dǎo)讀]前言 話說(shuō)上回HashMap跟面試官扯了半個(gè)小時(shí)之后,二面迎來(lái)了沒(méi)有削弱前的鐘馗,法師的鉤子讓安琪拉有點(diǎn)絕望。鐘馗穿著有些微微泛黃的格子道袍,站在安琪拉對(duì)面,開(kāi)始發(fā)難,其中讓安琪拉印象非常深刻的是法師的synchronized 鉤子。 開(kāi)場(chǎng) 面試官: ?你先自我介紹


前言

話說(shuō)上回HashMap跟面試官扯了半個(gè)小時(shí)之后,二面迎來(lái)了沒(méi)有削弱前的鐘馗,法師的鉤子讓安琪拉有點(diǎn)絕望。鐘馗穿著有些微微泛黃的格子道袍,站在安琪拉對(duì)面,開(kāi)始發(fā)難,其中讓安琪拉印象非常深刻的是法師的synchronized 鉤子。

開(kāi)場(chǎng)

面試官:  你先自我介紹一下吧!

安琪拉:  我是安琪拉,草叢三婊之一,最強(qiáng)中單(鐘馗冷哼)!哦,不對(duì),串場(chǎng)了,我是**,目前在--公司做--系統(tǒng)開(kāi)發(fā)。

面試官:  剛才聽(tīng)一面的同事說(shuō)你們上次聊到了synchronized,你借口說(shuō)要回去補(bǔ)籃,現(xiàn)在能跟我講講了吧?

安琪拉: 【上來(lái)就丟鉤子,都不寒暄幾句,問(wèn)我吃沒(méi)吃】嗯嗯,是有聊到 synchronized。

面試官:  那你跟我說(shuō)說(shuō)為什么會(huì)需要synchronized?什么場(chǎng)景下使用synchronized?

安琪拉:  這個(gè)就要說(shuō)到多線程訪問(wèn)共享資源了,當(dāng)一個(gè)資源有可能被多個(gè)線程同時(shí)訪問(wèn)并修改的話,需要用到,還是畫(huà)個(gè)圖給您看一下,請(qǐng)看圖:

一個(gè)synchronized跟面試官扯了半個(gè)小時(shí)
在這里插入圖片描述

安琪拉:  如上圖所示,比如在王者榮耀程序中,我們隊(duì)有二個(gè)線程分別統(tǒng)計(jì)后裔和安琪拉的經(jīng)濟(jì),A線程從內(nèi)存中read 當(dāng)前隊(duì)伍總經(jīng)濟(jì)加載到線程的本地棧,進(jìn)行 +100 操作之后,這時(shí)候B線程也從內(nèi)存中取出經(jīng)濟(jì)值 + 200,將200寫(xiě)回內(nèi)存,B線程前腳剛寫(xiě)完,后腳A線程將100 寫(xiě)回到內(nèi)存中,就出問(wèn)題了,我們隊(duì)的經(jīng)濟(jì)應(yīng)該是300, 但是內(nèi)存中存的卻是100,你說(shuō)糟不糟心。

面試官:  那你跟我講講用 synchronized 怎么解決這個(gè)問(wèn)題的?

安琪拉:  在訪問(wèn)競(jìng)態(tài)資源時(shí)加鎖,因?yàn)槎鄠€(gè)線程會(huì)修改經(jīng)濟(jì)值,因此經(jīng)濟(jì)值就是靜態(tài)資源,給您show 一下吧?下圖是不加鎖的代碼和控制臺(tái)的輸出,請(qǐng)您過(guò)目:

二個(gè)線程,A線程讓隊(duì)伍經(jīng)濟(jì) +1 ,B線程讓經(jīng)濟(jì) + 2,分別執(zhí)行一千次,正確的結(jié)果應(yīng)該是3000,結(jié)果得到的卻是 2845。

一個(gè)synchronized跟面試官扯了半個(gè)小時(shí)
在這里插入圖片描述

安琪拉:  這個(gè)就是加鎖之后的代碼和控制臺(tái)的輸出。

一個(gè)synchronized跟面試官扯了半個(gè)小時(shí)
(img-6NwdhDEz-1585279691724)(/Users/zw/Library/Application Support/typora-user-images/image-20200321210555529.png)]

面試官:  我看你用synchronized 鎖住的是代碼塊,synchronized 還有別的作用范圍嗎?

安琪拉:  嗯嗯,synchronized 有以下三種作用范圍:

  1. 在靜態(tài)方法上加鎖;

  2. 在非靜態(tài)方法上加鎖;

  3. 在代碼塊上加鎖;

    示例代碼如下

    public class SynchronizedSample {

    private final Object lock = new Object();

    private static int money = 0;
    //非靜態(tài)方法
    public synchronized void noStaticMethod(){
    money++;
    }
    //靜態(tài)方法
    public static synchronized void staticMethod(){
    money++;
    }

    public void codeBlock(){
    //代碼塊
    synchronized (lock){
    money++;
    }
    }
    }

面試官:  那你了解 synchronized 這三種作用范圍,加鎖方式的區(qū)別嗎?

安琪拉:  了解。首先要明確一點(diǎn):鎖是加在對(duì)象上面的,我們是在對(duì)象上加鎖。

重要事情說(shuō)三遍:在對(duì)象上加鎖 ? 3 (這也是為什么wait / notify 需要在鎖定對(duì)象后執(zhí)行,只有先拿到鎖才能釋放鎖)

這三種作用范圍的區(qū)別實(shí)際是被加鎖的對(duì)象的區(qū)別,請(qǐng)看下表:

作用范圍 鎖對(duì)象
非靜態(tài)方法 當(dāng)前對(duì)象 => this
靜態(tài)方法 類對(duì)象  => SynchronizedSample.class (一切皆對(duì)象,這個(gè)是類對(duì)象)
代碼塊 指定對(duì)象 => lock (以上面的代碼為例)

面試官:  那你清楚 JVM 是怎么通過(guò)synchronized 在對(duì)象上實(shí)現(xiàn)加鎖,保證多線程訪問(wèn)競(jìng)態(tài)資源安全的嗎?

安琪拉:  【天啦擼, 該來(lái)的還是要來(lái)】(⊙o⊙)…額,這個(gè)說(shuō)起來(lái)有點(diǎn)復(fù)雜,我怕時(shí)間不夠,要不下次再約?

面試官:  別下次了,今天我有的是時(shí)間,你慢慢講,我慢慢你說(shuō)。

安琪拉:  那要跟您好好說(shuō)道了。分二個(gè)時(shí)間段來(lái)跟您討論,先說(shuō)到盤(pán)古開(kāi)天辟地,女?huà)z造石補(bǔ)天,咳咳,不好意思扯遠(yuǎn)了。。。。。。

  1. 先說(shuō)在JDK6 以前,synchronized 那時(shí)還屬于重量級(jí)鎖,相當(dāng)于關(guān)二爺手中的青龍偃月刀,每次加鎖都依賴操作系統(tǒng)Mutex Lock實(shí)現(xiàn),涉及到操作系統(tǒng)讓線程從用戶態(tài)切換到內(nèi)核態(tài),切換成本很高;
  2. 到了JDK6,研究人員引入了偏向鎖和輕量級(jí)鎖,因?yàn)镾un 程序員發(fā)現(xiàn)大部分程序大多數(shù)時(shí)間都不會(huì)發(fā)生多個(gè)線程同時(shí)訪問(wèn)競(jìng)態(tài)資源的情況,每次線程都加鎖解鎖,每次這么搞都要操作系統(tǒng)在用戶態(tài)和內(nèi)核態(tài)之前來(lái)回切,太耗性能了。

面試官:  那你分別跟我講講JDK 6 以前 synchronized為什么這么重?JDK6 之后又是 偏向鎖和輕量級(jí)鎖又是怎么回事?

安琪拉:  好的。首先要了解 synchronized 的實(shí)現(xiàn)原理,需要理解二個(gè)預(yù)備知識(shí):

  1. 第一個(gè)預(yù)備知識(shí):需要知道 Java 對(duì)象頭,鎖的類型和狀態(tài)和對(duì)象頭的Mark Word息息相關(guān);

    synchronized 鎖 和 對(duì)象頭息息相關(guān)。我們來(lái)看下對(duì)象的結(jié)構(gòu):一個(gè)synchronized跟面試官扯了半個(gè)小時(shí)對(duì)象存儲(chǔ)在堆中,主要分為三部分內(nèi)容,對(duì)象頭、對(duì)象實(shí)例數(shù)據(jù)和對(duì)齊填充(數(shù)組對(duì)象多一個(gè)區(qū)域:記錄數(shù)組長(zhǎng)度),下面簡(jiǎn)單說(shuō)一下三部分內(nèi)容,雖然 synchronized 只與對(duì)象頭中的 Mard Word相關(guān)。

    1. 對(duì)象頭:

      對(duì)象頭分為二個(gè)部分,Mard Word 和 Klass Word,列出了詳細(xì)說(shuō)明:

      對(duì)象頭結(jié)構(gòu) 存儲(chǔ)信息-說(shuō)明
      Mard Word 存儲(chǔ)對(duì)象的hashCode、鎖信息或分代年齡或GC標(biāo)志等信息
      Klass Word 存儲(chǔ)指向?qū)ο笏鶎兕悾ㄔ獢?shù)據(jù))的指針,JVM通過(guò)這個(gè)確定這個(gè)對(duì)象屬于哪個(gè)類
    2. 對(duì)象實(shí)例數(shù)據(jù):

      如上圖所示,類中的 成員變量data 就屬于對(duì)象實(shí)例數(shù)據(jù);

    3. 對(duì)齊填充:

      JVM要求對(duì)象占用的空間必須是8 的倍數(shù),方便內(nèi)存分配(以字節(jié)為最小單位分配),因此這部分就是用于填滿不夠的空間湊數(shù)用的。

  2. 第二個(gè)預(yù)備知識(shí):需要了解 Monitor ,每個(gè)對(duì)象都有一個(gè)與之關(guān)聯(lián)的Monitor 對(duì)象;Monitor對(duì)象屬性如下所示( Hospot 1.7 代碼)  。

    //圖詳細(xì)介紹重要變量的作用
    ObjectMonitor() {
    _header = NULL;
    _count = 0; // 重入次數(shù)
    _waiters = 0, // 等待線程數(shù)
    _recursions = 0;
    _object = NULL;
    _owner = NULL; // 當(dāng)前持有鎖的線程
    _WaitSet = NULL; // 調(diào)用了 wait 方法的線程被阻塞 放置在這里
    _WaitSetLock = 0 ;
    _Responsible = NULL ;
    _succ = NULL ;
    _cxq = NULL ;
    FreeNext = NULL ;
    _EntryList = NULL ; // 等待鎖 處于block的線程 有資格成為候選資源的線程
    _SpinFreq = 0 ;
    _SpinClock = 0 ;
    OwnerIsThread = 0 ;
    }

    對(duì)象關(guān)聯(lián)的 ObjectMonitor 對(duì)象有一個(gè)線程內(nèi)部競(jìng)爭(zhēng)鎖的機(jī)制,如下圖所示:一個(gè)synchronized跟面試官扯了半個(gè)小時(shí)

面試官: 預(yù)備的二個(gè)知識(shí)我大體看了,后面給我講講 JDK 6 以前 synchronized具體實(shí)現(xiàn)邏輯吧。

安琪拉: 好的?!鹃_(kāi)始我的表演】

  1. 當(dāng)有二個(gè)線程A、線程B都要開(kāi)始給我們隊(duì)的經(jīng)濟(jì) money變量 + 錢,要進(jìn)行操作的時(shí)候 ,發(fā)現(xiàn)方法上加了synchronized鎖,這時(shí)線程調(diào)度到A線程執(zhí)行,A線程就搶先拿到了鎖。拿到鎖的步驟為:- 1.1 將 MonitorObject 中的 _owner設(shè)置成 A線程;- 1.2 將 mark word 設(shè)置為 Monitor 對(duì)象地址,鎖標(biāo)志位改為10;- 1.3 將B線程阻塞放到 ContentionList 隊(duì)列;

  2. JVM 每次從Waiting Queue 的尾部取出一個(gè)線程放到OnDeck作為候選者,但是如果并發(fā)比較高,Waiting Queue會(huì)被大量線程執(zhí)行CAS操作,為了降低對(duì)尾部元素的競(jìng)爭(zhēng),將Waiting Queue 拆分成ContentionList 和 EntryList 二個(gè)隊(duì)列, JVM將一部分線程移到EntryList 作為準(zhǔn)備進(jìn)OnDeck的預(yù)備線程。另外說(shuō)明幾點(diǎn):

  • 所有請(qǐng)求鎖的線程首先被放在ContentionList這個(gè)競(jìng)爭(zhēng)隊(duì)列中;

  • Contention List 中那些有資格成為候選資源的線程被移動(dòng)到 Entry List 中;

  • 任意時(shí)刻,最多只有一個(gè)線程正在競(jìng)爭(zhēng)鎖資源,該線程被成為 OnDeck;

  • 當(dāng)前已經(jīng)獲取到所資源的線程被稱為 Owner;

  • 處于 ContentionList、EntryList、WaitSet 中的線程都處于阻塞狀態(tài),該阻塞是由操作系統(tǒng)來(lái)完成的(Linux 內(nèi)核下采用 pthread_mutex_lock 內(nèi)核函數(shù)實(shí)現(xiàn)的);

  • 作為Owner 的A 線程執(zhí)行過(guò)程中,可能調(diào)用wait 釋放鎖,這個(gè)時(shí)候A線程進(jìn)入 Wait Set , 等待被喚醒。

  • 以上就是我想說(shuō)的 synchronized 在 JDK 6之前的實(shí)現(xiàn)原理。

    面試官:  那你知道 synchronized 是公平鎖還是非公平鎖嗎?

    安琪拉:  非公平的。主要有以下二點(diǎn)原因:

    • Synchronized 在線程競(jìng)爭(zhēng)鎖時(shí),首先做的不是直接進(jìn)ContentionList 隊(duì)列排隊(duì),而是嘗試自旋獲取鎖(可能ContentionList 有別的線程在等鎖),如果獲取不到才進(jìn)入 ContentionList,這明顯對(duì)于已經(jīng)進(jìn)入隊(duì)列的線程是不公平的;
    • 另一個(gè)不公平的是自旋獲取鎖的線程還可能直接搶占 OnDeck 線程的鎖資源。

    面試官:  你前面說(shuō)到 JDK 6 之后synchronized 做了優(yōu)化,跟我講講?

    安琪拉:  不要著急!容我點(diǎn)個(gè)治療,再跟你掰扯掰扯。前面說(shuō)了鎖跟對(duì)象頭的 Mark Word 密切相關(guān),我們把目光放到對(duì)象頭的 Mark Word 上, Mark Word 存儲(chǔ)結(jié)構(gòu)如下圖和源代碼注釋(以32位JVM為例,后面的討論都基于32位JVM的背景,64位會(huì)特殊說(shuō)明)。Mard Word會(huì)在不同的鎖狀態(tài)下,32位指定區(qū)域都有不同的含義,這個(gè)是為了節(jié)省存儲(chǔ)空間,用4 字節(jié)就表達(dá)了完整的狀態(tài)信息,當(dāng)然,對(duì)象某一時(shí)刻只會(huì)是下面5 種狀態(tài)種的某一種。

    一個(gè)synchronized跟面試官扯了半個(gè)小時(shí)下面是簡(jiǎn)化后的 Mark Word一個(gè)synchronized跟面試官扯了半個(gè)小時(shí)

    hash:保存對(duì)象的哈希碼
    age:保存對(duì)象的分代年齡
    biased_lock:偏向鎖標(biāo)識(shí)位
    lock:鎖狀態(tài)標(biāo)識(shí)位
    JavaThread*:保存持有偏向鎖的線程ID
    epoch:保存偏向時(shí)間戳

    安琪拉:  由于 synchronized 重量級(jí)鎖有以下二個(gè)問(wèn)題, 因此JDK 6 之后做了改進(jìn),引入了偏向鎖和輕量級(jí)鎖:

    • 依賴底層操作系統(tǒng)的 mutex 相關(guān)指令實(shí)現(xiàn),加鎖解鎖需要在用戶態(tài)和內(nèi)核態(tài)之間切換,性能損耗非常明顯。

    • 研究人員發(fā)現(xiàn),大多數(shù)對(duì)象的加鎖和解鎖都是在特定的線程中完成。也就是出現(xiàn)線程競(jìng)爭(zhēng)鎖的情況概率比較低。他們做了一個(gè)實(shí)驗(yàn),找了一些典型的軟件,測(cè)試同一個(gè)線程加鎖解鎖的重復(fù)率,如下圖所示,可以看到重復(fù)加鎖比例非常高。早期JVM 有 19% 的執(zhí)行時(shí)間浪費(fèi)在鎖上。

    一個(gè)synchronized跟面試官扯了半個(gè)小時(shí)
    在這里插入圖片描述

    Thin locks are a lot cheaper than inflated locks, but their performance suffers from the fact that every compare-and-swap operation must be executed atomically on multi-processor machines, although most objects are locked and unlocked only by one particular thread.

    It was reported that 19% of the total execution time was wasted by thread synchronization in an early version of Java virtual machine。

    面試官:  你跟我講講 JDK 6 以來(lái) synchronized 鎖狀態(tài)怎么從無(wú)鎖狀態(tài)到偏向鎖的嗎?

    安琪拉:  OK的啦!,我們來(lái)看下圖對(duì)象從無(wú)鎖到偏向鎖轉(zhuǎn)化的過(guò)程(JVM -XX:+UseBiasedLocking 開(kāi)啟偏向鎖):

    一個(gè)synchronized跟面試官扯了半個(gè)小時(shí)
    在這里插入圖片描述
    1. 首先A 線程訪問(wèn)同步代碼塊,使用CAS 操作將 Thread ID 放到 Mark Word 當(dāng)中;
    2. 如果CAS 成功,此時(shí)線程A 就獲取了鎖
    3. 如果線程CAS 失敗,證明有別的線程持有鎖,例如上圖的線程B 來(lái)CAS 就失敗的,這個(gè)時(shí)候啟動(dòng)偏向鎖撤銷 (revoke bias);
    4. 鎖撤銷流程:- 讓 A線程在全局安全點(diǎn)阻塞(類似于GC前線程在安全點(diǎn)阻塞) - 遍歷線程棧,查看是否有被鎖對(duì)象的鎖記錄( Lock Record),如果有Lock Record,需要修復(fù)鎖記錄和Markword,使其變成無(wú)鎖狀態(tài)。- 恢復(fù)A線程 - 將是否為偏向鎖狀態(tài)置為 0 ,開(kāi)始進(jìn)行輕量級(jí)加鎖流程 (后面講述) 下圖說(shuō)明了 Mark Word 在這個(gè)過(guò)程中的轉(zhuǎn)化 一個(gè)synchronized跟面試官扯了半個(gè)小時(shí) 面試官:   不錯(cuò),那你跟我講講偏向鎖撤銷怎么到輕量級(jí)鎖的?還有輕量級(jí)鎖什么時(shí)候會(huì)變成重量級(jí)鎖? 安琪拉:   繼續(xù)上面的流程,鎖撤銷之后(偏向鎖狀態(tài)為0),現(xiàn)在無(wú)論是A線程還是B線程執(zhí)行到同步代碼塊進(jìn)行加鎖,流程如下:
    • 線程在自己的棧楨中創(chuàng)建鎖記錄 LockRecord。
    • 線程A 將 Mark Word 拷貝到線程棧的 Lock Record中,這個(gè)位置叫 displayced hdr,如下圖所示: 一個(gè)synchronized跟面試官扯了半個(gè)小時(shí)
    • 將鎖記錄中的Owner指針指向加鎖的對(duì)象(存放對(duì)象地址)。
    • 將鎖對(duì)象的對(duì)象頭的MarkWord替換為指向鎖記錄的指針。這二步如下圖所示: 一個(gè)synchronized跟面試官扯了半個(gè)小時(shí)
  • 這時(shí)鎖標(biāo)志位變成 00 ,表示輕量級(jí)鎖
  • 面試官:  看來(lái)對(duì)synchronized 很有研究嘛。我鐘馗不信難不倒你,那輕量級(jí)鎖什么時(shí)候會(huì)升級(jí)為重量級(jí)鎖, 請(qǐng)回答?安琪拉:  當(dāng)鎖升級(jí)為輕量級(jí)鎖之后,如果依然有新線程過(guò)來(lái)競(jìng)爭(zhēng)鎖,首先新線程會(huì)自旋嘗試獲取鎖,嘗試到一定次數(shù)(默認(rèn)10次)依然沒(méi)有拿到,鎖就會(huì)升級(jí)成重量級(jí)鎖。面試官:  為什么這么設(shè)計(jì)?安琪拉:  一般來(lái)說(shuō),同步代碼塊內(nèi)的代碼應(yīng)該很快就執(zhí)行結(jié)束,這時(shí)候線程B 自旋一段時(shí)間是很容易拿到鎖的,但是如果不巧,沒(méi)拿到,自旋其實(shí)就是死循環(huán),很耗CPU的,因此就直接轉(zhuǎn)成重量級(jí)鎖咯,這樣就不用了線程一直自旋了。這就是鎖膨脹的過(guò)程,下圖是Mark Word 和鎖狀態(tài)的轉(zhuǎn)化圖一個(gè)synchronized跟面試官扯了半個(gè)小時(shí)主要圖我標(biāo)注出來(lái)的,鎖當(dāng)前為可偏向狀態(tài),偏向鎖狀態(tài)位置就是1,看到很多網(wǎng)上的文章都寫(xiě)錯(cuò)了,把這里寫(xiě)成只有鎖發(fā)生偏向才會(huì)置為1,一定要注意。面試官:  既然偏向鎖有撤銷,還會(huì)膨脹,性能損耗這么大,還需要用他們呢?安琪拉:  如果確定競(jìng)態(tài)資源會(huì)被高并發(fā)的訪問(wèn),建議通過(guò)-XX:-UseBiasedLocking 參數(shù)關(guān)閉偏向鎖,偏向鎖的好處是并發(fā)度很低的情況下,同一個(gè)線程獲取鎖不需要內(nèi)存拷貝的操作,免去了輕量級(jí)鎖的在線程棧中建Lock Record,拷貝Mark Down的內(nèi)容,也免了重量級(jí)鎖的底層操作系統(tǒng)用戶態(tài)到內(nèi)核態(tài)的切換,因?yàn)榍懊嬲f(shuō)了,需要使用系統(tǒng)指令。另外Hotspot 也做了另一項(xiàng)優(yōu)化,基于鎖對(duì)象的epoch 批量偏移和批量撤銷偏移,這樣大大降低了偏向鎖的CAS和鎖撤銷帶來(lái)的損耗,圖是研究人員做的壓測(cè):一個(gè)synchronized跟面試官扯了半個(gè)小時(shí)

    Eliminating Synchronization-Related Atomic Operations with Biased Locking and Bulk Rebiasing


    安琪拉:  他們?cè)趲卓畹湫蛙浖献隽藴y(cè)試,發(fā)現(xiàn)基于epoch 批量撤銷偏向鎖和批量加偏向鎖能大幅提升吞吐量,但是并發(fā)量特別大的時(shí)候性能就沒(méi)有什么特別的提升了。面試官:可以可以,那你看過(guò)synchronized 底層實(shí)現(xiàn)源碼沒(méi)有?安琪拉:   那當(dāng)然啦,源碼是我的二技能,高爆發(fā)的傷害能不能打出來(lái)就看它了,我們一步一步來(lái)。我們把文章開(kāi)頭的示例代碼編譯成class 文件,然后通過(guò)javap -v SynchronizedSample.class  來(lái)看下synchronized 到底在源碼層面如何實(shí)現(xiàn)的?如下圖所示:一個(gè)synchronized跟面試官扯了半個(gè)小時(shí)安琪拉:  synchronized 在代碼塊上是通過(guò) monitorenter 和 monitorexit指令實(shí)現(xiàn),在靜態(tài)方法和 方法上加鎖是在方法的flags 中加入 ACC_SYNCHRONIZED 。JVM 運(yùn)行方法時(shí)檢查方法的flags,遇到同步標(biāo)識(shí)開(kāi)始啟動(dòng)前面的加鎖流程,在方法內(nèi)部遇到monitorenter指令開(kāi)始加鎖。

    monitorenter 指令函數(shù)源代碼在 InterpreterRuntime::monitorenter

    IRT_ENTRY_NO_ASYNC(void, InterpreterRuntime::monitorenter(JavaThread* thread, BasicObjectLock* elem))
    #ifdef ASSERT
    thread->last_frame().interpreter_frame_verify_monitor(elem);
    #endif
    if (PrintBiasedLockingStatistics) {
    Atomic::inc(BiasedLocking::slow_path_entry_count_addr());
    }
    Handle h_obj(thread, elem->obj());
    assert(Universe::heap()->is_in_reserved_or_null(h_obj()),
    "must be NULL or an object");
    //是否開(kāi)啟了偏向鎖
    if (UseBiasedLocking) {
    // 嘗試偏向鎖
    ObjectSynchronizer::fast_enter(h_obj, elem->lock(), true, CHECK);
    } else {
    // 輕量鎖邏輯
    ObjectSynchronizer::slow_enter(h_obj, elem->lock(), CHECK);
    }
    assert(Universe::heap()->is_in_reserved_or_null(elem->obj()),
    "must be NULL or an object");
    #ifdef ASSERT
    thread->last_frame().interpreter_frame_verify_monitor(elem);
    #endif
    IRT_END

    偏向鎖代碼

    // -----------------------------------------------------------------------------
    // Fast Monitor Enter/Exit
    // This the fast monitor enter. The interpreter and compiler use
    // some assembly copies of this code. Make sure update those code
    // if the following function is changed. The implementation is
    // extremely sensitive to race condition. Be careful.

    void ObjectSynchronizer::fast_enter(Handle obj, BasicLock* lock, bool attempt_rebias, TRAPS) {
    //是否使用偏向鎖
    if (UseBiasedLocking) {
    // 如果不在全局安全點(diǎn)
    if (!SafepointSynchronize::is_at_safepoint()) {
    // 獲取偏向鎖
    BiasedLocking::Condition cond = BiasedLocking::revoke_and_rebias(obj, attempt_rebias, THREAD);
    if (cond == BiasedLocking::BIAS_REVOKED_AND_REBIASED) {
    return;
    }
    } else {
    assert(!attempt_rebias, "can not rebias toward VM thread");
    // 在全局安全點(diǎn),撤銷偏向鎖
    BiasedLocking::revoke_at_safepoint(obj);
    }
    assert(!obj->mark()->has_bias_pattern(), "biases should be revoked by now");
    }
    // 進(jìn)輕量級(jí)鎖流程
    slow_enter (obj, lock, THREAD) ;
    }

    偏向鎖的實(shí)現(xiàn)具體代碼在 BiasedLocking::revoke_and_rebias 中,因?yàn)楹瘮?shù)非常長(zhǎng),就不貼出來(lái),有興趣的可以在Hotspot 1.8-biasedLocking.cpp[2]去看。輕量級(jí)鎖代碼流程

    void ObjectSynchronizer::slow_enter(Handle obj, BasicLock* lock, TRAPS) {
    //獲取對(duì)象的markOop數(shù)據(jù)mark
    markOop mark = obj->mark();
    assert(!mark->has_bias_pattern(), "should not see bias pattern here");

    //判斷mark是否為無(wú)鎖狀態(tài) & 不可偏向(鎖標(biāo)識(shí)為01,偏向鎖標(biāo)志位為0)
    if (mark->is_neutral()) {
    // Anticipate successful CAS -- the ST of the displaced mark must
    // be visible <= the ST performed by the CAS.
    // 保存Mark 到 線程棧 Lock Record 的displaced_header中
    lock->set_displaced_header(mark);
    // CAS 將 Mark Down 更新為 指向 lock 對(duì)象的指針,成功則獲取到鎖
    if (mark == (markOop) Atomic::cmpxchg_ptr(lock, obj()->mark_addr(), mark)) {
    TEVENT (slow_enter: release stacklock) ;
    return ;
    }
    // Fall through to inflate() ...
    } else
    // 根據(jù)對(duì)象mark 判斷已經(jīng)有鎖 & mark 中指針指的當(dāng)前線程的Lock Record(當(dāng)前線程已經(jīng)獲取到了,不必重試獲?。?br> if (mark->has_locker() && THREAD->is_lock_owned((address)mark->locker())) {
    assert(lock != mark->locker(), "must not re-lock the same lock");
    assert(lock != (BasicLock*)obj->mark(), "don't relock with same BasicLock");
    lock->set_displaced_header(NULL);
    return;
    }

    lock->set_displaced_header(markOopDesc::unused_mark());
    // 鎖膨脹
    ObjectSynchronizer::inflate(THREAD, obj())->enter(THREAD);

    做個(gè)假設(shè),現(xiàn)在線程A 和B 同時(shí)執(zhí)行到臨界區(qū)if (mark->is_neutral()):1、線程A和B都把Mark Word復(fù)制到各自的_displaced_header字段,該數(shù)據(jù)保存在線程的棧幀上,是線程私有的;2、Atomic::cmpxchg_ptr 屬于原子操作,保障了只有一個(gè)線程可以把Mark Word中替換成指向自己線程棧 displaced_header中的,假設(shè)A線程執(zhí)行成功,相當(dāng)于A獲取到了鎖,開(kāi)始繼續(xù)執(zhí)行同步代碼塊;3、線程B執(zhí)行失敗,退出臨界區(qū),通過(guò)ObjectSynchronizer::inflate方法開(kāi)始膨脹鎖;

    面試官:synchronized 源碼這部分可以了,不下去了。你跟我講講Java中除了synchronized 還有別的鎖嗎?

    安琪拉:   還有ReentrantLock也可以實(shí)現(xiàn)加鎖。

    面試官:那寫(xiě)段代碼實(shí)現(xiàn)之前加經(jīng)濟(jì)的同樣效果。

    安琪拉:   coding  如圖:一個(gè)synchronized跟面試官扯了半個(gè)小時(shí)面試官:哦,那你跟我說(shuō)說(shuō)ReentrantLock 的底層實(shí)現(xiàn)原理?

    安琪拉:   天色已晚,我們能改日再聊嗎?

    面試官:那你回去等通知吧。

    安琪拉:   【內(nèi)心是崩潰的】,看來(lái)這次面試就黃了,,心累。一個(gè)synchronized跟面試官扯了半個(gè)小時(shí)

    未完,下一篇介紹ReentrantLock相關(guān)的底層原理,看安琪拉如何大戰(zhàn)鐘馗面試官三百回合。

    參考資料

    [1]

    一個(gè)HashMap跟面試官扯了半個(gè)小時(shí): https://blog.csdn.net/zhengwangzw/article/details/104889549

    [2]

    Hotspot 1.8-biasedLocking.cpp: https://github.com/sourcemirror/jdk-8-hotspot/blob/master/src/share/vm/runtime/biasedLocking.cpp

    特別推薦一個(gè)分享架構(gòu)+算法的優(yōu)質(zhì)內(nèi)容,還沒(méi)關(guān)注的小伙伴,可以長(zhǎng)按關(guān)注一下:

    一個(gè)synchronized跟面試官扯了半個(gè)小時(shí)

    長(zhǎng)按訂閱更多精彩▼

    一個(gè)synchronized跟面試官扯了半個(gè)小時(shí)

    如有收獲,點(diǎn)個(gè)在看,誠(chéng)摯感謝

    免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺(tái)僅提供信息存儲(chǔ)服務(wù)。文章僅代表作者個(gè)人觀點(diǎn),不代表本平臺(tái)立場(chǎng),如有問(wèn)題,請(qǐng)聯(lián)系我們,謝謝!

    本站聲明: 本文章由作者或相關(guān)機(jī)構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點(diǎn),本站亦不保證或承諾內(nèi)容真實(shí)性等。需要轉(zhuǎn)載請(qǐng)聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請(qǐng)及時(shí)聯(lián)系本站刪除。
    換一批
    延伸閱讀

    9月2日消息,不造車的華為或?qū)⒋呱龈蟮莫?dú)角獸公司,隨著阿維塔和賽力斯的入局,華為引望愈發(fā)顯得引人矚目。

    關(guān)鍵字: 阿維塔 塞力斯 華為

    倫敦2024年8月29日 /美通社/ -- 英國(guó)汽車技術(shù)公司SODA.Auto推出其旗艦產(chǎn)品SODA V,這是全球首款涵蓋汽車工程師從創(chuàng)意到認(rèn)證的所有需求的工具,可用于創(chuàng)建軟件定義汽車。 SODA V工具的開(kāi)發(fā)耗時(shí)1.5...

    關(guān)鍵字: 汽車 人工智能 智能驅(qū)動(dòng) BSP

    北京2024年8月28日 /美通社/ -- 越來(lái)越多用戶希望企業(yè)業(yè)務(wù)能7×24不間斷運(yùn)行,同時(shí)企業(yè)卻面臨越來(lái)越多業(yè)務(wù)中斷的風(fēng)險(xiǎn),如企業(yè)系統(tǒng)復(fù)雜性的增加,頻繁的功能更新和發(fā)布等。如何確保業(yè)務(wù)連續(xù)性,提升韌性,成...

    關(guān)鍵字: 亞馬遜 解密 控制平面 BSP

    8月30日消息,據(jù)媒體報(bào)道,騰訊和網(wǎng)易近期正在縮減他們對(duì)日本游戲市場(chǎng)的投資。

    關(guān)鍵字: 騰訊 編碼器 CPU

    8月28日消息,今天上午,2024中國(guó)國(guó)際大數(shù)據(jù)產(chǎn)業(yè)博覽會(huì)開(kāi)幕式在貴陽(yáng)舉行,華為董事、質(zhì)量流程IT總裁陶景文發(fā)表了演講。

    關(guān)鍵字: 華為 12nm EDA 半導(dǎo)體

    8月28日消息,在2024中國(guó)國(guó)際大數(shù)據(jù)產(chǎn)業(yè)博覽會(huì)上,華為常務(wù)董事、華為云CEO張平安發(fā)表演講稱,數(shù)字世界的話語(yǔ)權(quán)最終是由生態(tài)的繁榮決定的。

    關(guān)鍵字: 華為 12nm 手機(jī) 衛(wèi)星通信

    要點(diǎn): 有效應(yīng)對(duì)環(huán)境變化,經(jīng)營(yíng)業(yè)績(jī)穩(wěn)中有升 落實(shí)提質(zhì)增效舉措,毛利潤(rùn)率延續(xù)升勢(shì) 戰(zhàn)略布局成效顯著,戰(zhàn)新業(yè)務(wù)引領(lǐng)增長(zhǎng) 以科技創(chuàng)新為引領(lǐng),提升企業(yè)核心競(jìng)爭(zhēng)力 堅(jiān)持高質(zhì)量發(fā)展策略,塑強(qiáng)核心競(jìng)爭(zhēng)優(yōu)勢(shì)...

    關(guān)鍵字: 通信 BSP 電信運(yùn)營(yíng)商 數(shù)字經(jīng)濟(jì)

    北京2024年8月27日 /美通社/ -- 8月21日,由中央廣播電視總臺(tái)與中國(guó)電影電視技術(shù)學(xué)會(huì)聯(lián)合牽頭組建的NVI技術(shù)創(chuàng)新聯(lián)盟在BIRTV2024超高清全產(chǎn)業(yè)鏈發(fā)展研討會(huì)上宣布正式成立。 活動(dòng)現(xiàn)場(chǎng) NVI技術(shù)創(chuàng)新聯(lián)...

    關(guān)鍵字: VI 傳輸協(xié)議 音頻 BSP

    北京2024年8月27日 /美通社/ -- 在8月23日舉辦的2024年長(zhǎng)三角生態(tài)綠色一體化發(fā)展示范區(qū)聯(lián)合招商會(huì)上,軟通動(dòng)力信息技術(shù)(集團(tuán))股份有限公司(以下簡(jiǎn)稱"軟通動(dòng)力")與長(zhǎng)三角投資(上海)有限...

    關(guān)鍵字: BSP 信息技術(shù)
    關(guān)閉
    關(guān)閉