一個(gè)synchronized跟面試官扯了半個(gè)小時(shí)
掃描二維碼
隨時(shí)隨地手機(jī)看文章
前言
話說(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)看圖:
安琪拉: 如上圖所示,比如在王者榮耀程序中,我們隊(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è)就是加鎖之后的代碼和控制臺(tái)的輸出。
面試官: 我看你用synchronized 鎖住的是代碼塊,synchronized 還有別的作用范圍嗎?
安琪拉: 嗯嗯,synchronized 有以下三種作用范圍:
-
在靜態(tài)方法上加鎖;
-
在非靜態(tài)方法上加鎖;
-
在代碼塊上加鎖;
示例代碼如下
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)了。。。。。。
-
先說(shuō)在JDK6 以前,synchronized 那時(shí)還屬于重量級(jí)鎖,相當(dāng)于關(guān)二爺手中的青龍偃月刀,每次加鎖都依賴操作系統(tǒng)Mutex Lock實(shí)現(xiàn),涉及到操作系統(tǒng)讓線程從用戶態(tài)切換到內(nèi)核態(tài),切換成本很高; -
到了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í):
-
第一個(gè)預(yù)備知識(shí):需要知道 Java 對(duì)象頭,鎖的類型和狀態(tài)和對(duì)象頭的Mark Word息息相關(guān);
synchronized 鎖 和 對(duì)象頭息息相關(guān)。我們來(lái)看下對(duì)象的結(jié)構(gòu):對(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)。
-
對(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è)類 -
對(duì)象實(shí)例數(shù)據(jù):
如上圖所示,類中的 成員變量data 就屬于對(duì)象實(shí)例數(shù)據(jù);
-
對(duì)齊填充:
JVM要求對(duì)象占用的空間必須是8 的倍數(shù),方便內(nèi)存分配(以字節(jié)為最小單位分配),因此這部分就是用于填滿不夠的空間湊數(shù)用的。
-
第二個(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ī)制,如下圖所示:
面試官: 預(yù)備的二個(gè)知識(shí)我大體看了,后面給我講講 JDK 6 以前 synchronized具體實(shí)現(xiàn)邏輯吧。
安琪拉: 好的?!鹃_(kāi)始我的表演】
-
當(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ì)列; -
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)種的某一種。
下面是簡(jiǎn)化后的 Mark Word
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)在鎖上。
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)啟偏向鎖):
-
首先A 線程訪問(wèn)同步代碼塊,使用CAS 操作將 Thread ID 放到 Mark Word
當(dāng)中; -
如果CAS 成功,此時(shí)線程A 就獲取了鎖 -
如果線程CAS 失敗,證明有別的線程持有鎖,例如上圖的線程B 來(lái)CAS 就失敗的,這個(gè)時(shí)候啟動(dòng)偏向鎖撤銷 (revoke bias); -
鎖撤銷流程:- 讓 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)化 面試官: 不錯(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,如下圖所示: -
將鎖記錄中的Owner指針指向加鎖的對(duì)象(存放對(duì)象地址)。 -
將鎖對(duì)象的對(duì)象頭的MarkWord替換為指向鎖記錄的指針。這二步如下圖所示:
面試官: 看來(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)化圖主要圖我標(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è):
安琪拉: 他們?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)的?如下圖所示:安琪拉: 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 如圖:面試官:哦,那你跟我說(shuō)說(shuō)ReentrantLock 的底層實(shí)現(xiàn)原理?
安琪拉: 天色已晚,我們能改日再聊嗎?
面試官:那你回去等通知吧。
安琪拉: 【內(nèi)心是崩潰的】,看來(lái)這次面試就黃了,,心累。
未完,下一篇介紹ReentrantLock相關(guān)的底層原理,看安琪拉如何大戰(zhàn)鐘馗面試官三百回合。
參考資料
一個(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)注一下:
長(zhǎng)按訂閱更多精彩▼
如有收獲,點(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)系我們,謝謝!