Java偏向鎖狀態(tài)轉(zhuǎn)移原理

為什么需要偏向鎖

當(dāng)多個(gè)處理器同時(shí)處理的時(shí)候忘蟹,通常需要處理互斥的問題。

一般的解決方式都會(huì)包含acquire和release這個(gè)兩種操作,操作保證滞伟,一個(gè)線程在acquire執(zhí)行之后,在它執(zhí)行release之前炕贵,其它線程不能完成acquire操作梆奈。這個(gè)過程經(jīng)常就涉及到鎖。研究表明(L. Lamport A fast mutual execlusion algorithm)称开,通過 fast locks算法可以做到亩钟,lock和unlock操作所需的時(shí)間與潛在的競(jìng)爭(zhēng)處理器數(shù)無關(guān)。

java內(nèi)置了monitor來處理多線程競(jìng)爭(zhēng)的情況.

一種優(yōu)化方式是使用 輕量鎖來在大多數(shù)情況下避免重量鎖的使用鳖轰,輕量鎖的主要機(jī)制是在monitor entry的時(shí)候使用原子操作清酥,某些退出操作也是這樣,如果有競(jìng)爭(zhēng)發(fā)生就轉(zhuǎn)而退避到使用操作系統(tǒng)的互斥量

輕量鎖認(rèn)為大多數(shù)情況下都不會(huì)產(chǎn)生競(jìng)爭(zhēng)

在鎖的使用中一般會(huì)使用幾種原子指令:

CAS:檢查給定指針位置的值和傳入的值是否一致蕴侣,如果一致焰轻,就修改

SWAP:替換指針原位置的值,并返回舊的值

membar:內(nèi)存屏障約束了處理器在處理指令時(shí)的重排序情況昆雀,比如禁止同讀操作被重排序到寫操作之后

Java中使用 two-word 對(duì)象頭

是 mark word,它包括同步信息辱志,垃圾回收信息、hash code信息

指向?qū)ο蟮闹羔槍?duì)象

這些指令的花銷很昂貴狞膘,因?yàn)樗麄兊膶?shí)現(xiàn)通常會(huì)耗盡處理器的重排序緩沖區(qū)揩懒,從而限制了處理器原本能夠像流水線一樣處理指令的能力。研究數(shù)據(jù)發(fā)現(xiàn)(Eliminating_synchronization-related_atomic_operations_with_biased_locking_and_bulk_rebiasing)原子操作在真實(shí)的應(yīng)用中客冈,比如javac 旭从,會(huì)導(dǎo)致性能下降20%。

另一種優(yōu)化的方式是使用偏向鎖,它不僅認(rèn)為大多數(shù)情況下是沒有競(jìng)爭(zhēng)的和悦,而且在整個(gè)的monitor的一生中退疫,都只會(huì)有一個(gè)線程來執(zhí)行enter和exit,這樣的監(jiān)視器就很適合偏向于這個(gè)線程了鸽素。當(dāng)然如果這時(shí)有另外一個(gè)線程嘗試進(jìn)入偏向鎖褒繁,即使沒有發(fā)生競(jìng)爭(zhēng),也需要執(zhí)行 偏向鎖撤銷操作

輕量鎖

當(dāng)輕量鎖通過monitorenter指令獲取鎖的時(shí)候馍忽,鎖記錄肯定會(huì)被記錄到線程的棧里面去棒坏,以表示鎖獲取操作。鎖記錄會(huì)持有原始對(duì)象的mark word和一些必備的元數(shù)據(jù)來識(shí)別鎖住的對(duì)象遭笋。在獲取鎖的時(shí)候坝冕,mark word會(huì)被拷貝一份到鎖記錄(這個(gè)操作稱為 displaced mark word)然后執(zhí)行CAS操作嘗試是的對(duì)象的mark word指針指向鎖記錄。如果CAS成功瓦呼,當(dāng)前線程就持有了鎖喂窟,如果失敗,其它線程獲取鎖,這是鎖就“膨脹”央串,轉(zhuǎn)而使用了操作系統(tǒng)的互斥量和條件磨澡,在“膨脹”的過程中,對(duì)象本身的mark word會(huì)經(jīng)過CAS操作指向含有mutex和condition的數(shù)據(jù)結(jié)構(gòu)质和。

當(dāng)執(zhí)行unlock的時(shí)候稳摄,扔通過CAS來操作mark word,如果CAS成功了,說明沒有競(jìng)爭(zhēng)饲宿,同時(shí)維持輕量鎖厦酬;如果失敗了,鎖就處于競(jìng)爭(zhēng)態(tài)褒傅,當(dāng)被持有時(shí)弃锐,會(huì)以一種“非常慢”的方式來正確的釋放鎖并通知其他等待線程來獲取鎖

同一個(gè)線程重新處理的方式很直白袄友,在輕量鎖發(fā)現(xiàn)要獲取的鎖已經(jīng)被當(dāng)前線程持有的時(shí)候殿托,它會(huì)存一個(gè)0進(jìn)去,而不對(duì)mark word做任何處理剧蚣,同樣在unlock的時(shí)候支竹,如果有看到0,也不會(huì)更新對(duì)象的mark word.并每次重入鸠按,都會(huì)明確的記錄count礼搁。

偏向鎖的實(shí)現(xiàn)

線程指針是NULL(0)表示當(dāng)前沒有線程被偏向這個(gè)對(duì)象

當(dāng)分配一個(gè)對(duì)象并且這個(gè)對(duì)象能夠執(zhí)行偏向的時(shí)候并且還沒有偏向時(shí),會(huì)執(zhí)行CAS是的當(dāng)前線程ID放入到mark word的線程ID區(qū)域目尖。

如果成功馒吴,對(duì)象本身就會(huì)被偏向到當(dāng)前線程,當(dāng)前線程會(huì)成為偏向所有者

> 線程ID直接指向JVM內(nèi)部表示的線程;java虛擬機(jī)中則是在最后3bit填充0x5表示偏向模式。

如果CAS失敗了饮戳,即另一個(gè)線程已經(jīng)成為偏向的所有者豪治,這意味著這個(gè)線程的偏向必須撤銷。對(duì)象的狀態(tài)會(huì)變成輕量鎖的模式扯罐,為了達(dá)到這一點(diǎn)负拟,嘗試把對(duì)象偏向于自己的線程必須能夠操作偏向所有者的棧,為此需要全局安全點(diǎn)已經(jīng)觸達(dá)(沒有線程在執(zhí)行字節(jié)碼)歹河。此時(shí)偏向擁有者會(huì)像輕量級(jí)鎖操作那樣掩浙,它的堆棧會(huì)填入鎖記錄,然后對(duì)象本身的mark word會(huì)被更新成指向棧上最老的鎖記錄秸歧,然后線程本身在安全點(diǎn)的阻塞會(huì)被釋放

> 如果沒有被原有的偏向鎖持有者持有厨姚,會(huì)撤銷對(duì)象重新回到可偏向但是還沒有偏向的狀態(tài),然后嘗試重新獲取鎖键菱。如果對(duì)象當(dāng)前鎖住了是進(jìn)入輕量鎖遣蚀,如果沒有鎖住是進(jìn)入未被鎖定的,不可偏向?qū)ο?/p>

下一個(gè)獲取鎖的操作會(huì)與檢測(cè)對(duì)象的mark word,如果對(duì)象是可偏向的纱耻,并且偏向的所有者是當(dāng)前那線程芭梯,會(huì)沒有任何額外操作而立馬獲取鎖。

這個(gè)時(shí)候偏向鎖的持有者的棧不會(huì)初始化鎖記錄弄喘,因?yàn)閷?duì)象偏向的時(shí)候玖喘,是永遠(yuǎn)不會(huì)檢驗(yàn)鎖記錄的

unlock的時(shí)候,會(huì)測(cè)試mark word的狀態(tài)蘑志,看是否仍然有偏向模式累奈。如果有,就不會(huì)再做其它的測(cè)試急但,甚至不需要管線程ID是不是當(dāng)前線程ID

這里通過解釋器的保證monitorexit操作只會(huì)在當(dāng)前線程執(zhí)行澎媒,所以這也是一個(gè)不需要檢查的理由

不適用偏向鎖的模式

生產(chǎn)生-消費(fèi)者模式,會(huì)有過個(gè)線程參與競(jìng)爭(zhēng)波桩;

一個(gè)線程分配多個(gè)對(duì)象戒努,然后給每個(gè)對(duì)象執(zhí)行初始的同步操作,再有其它線程來處理子流程

批量回到可偏向狀態(tài)還是撤銷可偏向镐躲?

經(jīng)驗(yàn)發(fā)現(xiàn)為特定的數(shù)據(jù)結(jié)構(gòu)選擇性的禁用偏向鎖(Store-fremm biased lock SFBL)來避免不合適的情況是合理的储玫。為此需要考慮每個(gè)數(shù)據(jù)結(jié)構(gòu)到底是執(zhí)行撤銷偏向的消耗小還是重新回到可偏向的狀態(tài)消耗下。一種啟發(fā)式的方式來決定到底是執(zhí)行那種方式萤皂,在每個(gè)類的元數(shù)據(jù)里面都會(huì)包含一個(gè)counter和時(shí)間戳撒穷,每次偏向鎖的實(shí)例執(zhí)行一次偏向撤銷,都會(huì)自增裆熙,時(shí)間戳用于記錄上次執(zhí)行bulk rebias的時(shí)間端礼。

撤銷計(jì)數(shù)并統(tǒng)計(jì)那些處于可偏向但是未偏向狀態(tài)的撤銷禽笑,這些操作的撤銷只需要一次CAS就可以

counter本身有兩個(gè)閾值,一個(gè)是bulk rebias閾值蛤奥,一個(gè)是bulk revocation蒲每。剛開始的時(shí)候,這種啟發(fā)式的算法可以單獨(dú)的決定執(zhí)行rebias還是revoke,一單bulk rebias的閾值達(dá)到喻括,就會(huì)執(zhí)行bulk rebias邀杏,轉(zhuǎn)移到 rebiasable狀態(tài)

time閾值用來重置撤銷的計(jì)數(shù)counter,如果自從上次執(zhí)行bulk bias已經(jīng)超過了這個(gè)閾值時(shí)間,就會(huì)發(fā)生counter的重置唬血。

這意味著從上次執(zhí)行bulk rebias到現(xiàn)在并沒有執(zhí)行多次的撤銷操作望蜡,也就是說執(zhí)行bias仍然是個(gè)不錯(cuò)的選擇

但是如果在執(zhí)行了bulk rebias之后,在時(shí)間閾值之內(nèi)拷恨,仍然一直有撤銷數(shù)量增長(zhǎng)脖律,一旦達(dá)到了bulk revocation的閾值,就會(huì)執(zhí)行bulk revocation,此時(shí)這個(gè)類的對(duì)象不會(huì)再被允許使用偏向鎖腕侄。

Hotspot中的閾值如下 Bulk rebias threshold 20 Bulk revoke threshold 40 Decay time 25s

撤銷偏向本身是一個(gè)消耗很大的事情小泉,因?yàn)樗仨殥炱鹁€程,遍歷棧找到并修改lock records(鎖記錄)

最明顯的查找某個(gè)數(shù)據(jù)結(jié)構(gòu)的所有對(duì)象實(shí)例的方式就是遍歷堆冕杠,這種方式在堆比較小的時(shí)候還可以微姊,但是堆變大就顯得性能不好。為類解決這個(gè)為題分预,使用epoch兢交。epoch是一個(gè)時(shí)間戳,用來表明偏向的合法性笼痹,只要這個(gè)數(shù)據(jù)接口是可偏向的配喳,那么就會(huì)在mark word上有一個(gè)對(duì)應(yīng)的epoch bit位

這個(gè)時(shí)候,一個(gè)對(duì)象被認(rèn)為已經(jīng)偏向了線程T必須滿足兩個(gè)條件凳干,1: mark word中偏向所有這的標(biāo)記必須是這個(gè)線程晴裹,2:實(shí)例的epoch必須是和數(shù)據(jù)結(jié)構(gòu)的epoch相等

epoch本身的大小是限制的,也就是有可能出現(xiàn)循環(huán)救赐,但這并不影響方案的正確性

通過這種方式涧团,類C的bulk rebiasing操作會(huì)少去很多的花銷。具體操作如下

增大類C的epoch净响,它本身是一個(gè)固定長(zhǎng)度的integer,和對(duì)象頭中的epoch擁有一樣的bit位數(shù)

掃描所有的線程棧來定位當(dāng)前類C的實(shí)例中已經(jīng)鎖住的少欺,更新他們的epoch為類C的新的epoch或者是,根據(jù)啟發(fā)式策略撤銷偏向

這樣就不用掃描堆了馋贤,對(duì)于那些沒有被改變epoch的實(shí)例(和類的epoch不同),會(huì)被自動(dòng)當(dāng)做可偏向但是還沒有偏向的狀態(tài)

這種狀態(tài)可看做 rebiaseable

當(dāng)前HotSpot虛擬機(jī)的實(shí)現(xiàn)

批量撤銷本身存在著性能問題畏陕,一般的解決方式如下

添加epoch,如前所訴

線程第一次獲取的時(shí)候不偏向配乓,而是在執(zhí)行一定數(shù)量后都有同一個(gè)線程獲取再偏向

允許鎖具有永遠(yuǎn)改變(或者很少)的固定偏向線程,并且允許非偏向線程獲取鎖而不是撤銷鎖。

這種方式必須確保獲取鎖的線程必須確保進(jìn)去臨界區(qū)之前沒有其它線程持有鎖犹芹,并且不能使用 read-modify-write的指令崎页,只能使用read和write

當(dāng)前Hotspot JVM中的在32位和64位有不同的形式

64bit為

32bit為

輕量鎖(thin locks),細(xì)節(jié)如前所述。它在HotSpot中使用displaced header的方式實(shí)現(xiàn)腰埂,又被稱作棧鎖

mark完整的狀態(tài)轉(zhuǎn)換關(guān)系如下

剛分配對(duì)象飒焦,此時(shí)對(duì)象是可偏向并且未偏向的

對(duì)象偏向于線程T,并記下epoch

此時(shí)有新線程來競(jìng)爭(zhēng)

3.1一種策略是T執(zhí)行對(duì)應(yīng)的unlock屿笼,并重新分配給新的線程,以便不需要執(zhí)行撤銷操作

3.2 如果已經(jīng)偏向的對(duì)象被其它線程通過wait或者notify操作了牺荠,里面進(jìn)入膨脹裝態(tài),使用重量鎖

此時(shí)有新的線程來競(jìng)爭(zhēng)驴一,一種策略是使用啟發(fā)式的方式來統(tǒng)計(jì)撤銷的次數(shù)

4.1 當(dāng)撤銷達(dá)到bulk rebias的閾值時(shí)休雌,執(zhí)行bulk rebias

4.2 當(dāng)撤銷達(dá)到bulk revoke,并且此時(shí)所仍然被持有(原偏向鎖持有者)肝断,轉(zhuǎn)向輕量鎖(hashcode的計(jì)算依賴于膨脹來支持修改displaced mark word)

4.3 當(dāng)撤銷達(dá)到bulk revoke杈曲,并且此時(shí)所沒有被持有(原偏向鎖持有者),轉(zhuǎn)向未被鎖定不可偏向的狀態(tài)胸懈,此時(shí)沒有進(jìn)行hashcode計(jì)算

對(duì)于經(jīng)過bulk rebias的對(duì)象担扑,檢查期間沒有鎖定的實(shí)例,它的epoch會(huì)和class的不一樣趣钱,變成過期魁亦,但是可以偏向

5.1 如果 發(fā)生垃圾回收,lock會(huì)被初始化成可偏向但未偏向的狀態(tài)(這也可以降低epoch循環(huán)使用的影響)

5.2 如果重新被線程獲取偏向鎖羔挡,回到偏向鎖獲取狀態(tài)

處于輕量鎖狀態(tài)洁奈,它可能沒有hashcode計(jì)算,可能有绞灼,這依賴于inflat

6.1 沒有hashcode利术,此時(shí)解鎖回到?jīng)]有hashcode計(jì)算的不可偏向的狀態(tài)

6.2 又被其它線程占有,轉(zhuǎn)移到重量鎖(比如使用POXIS操作系統(tǒng)的mutex和condition)

未被鎖定不可偏向的狀態(tài)同時(shí)沒有hashcode計(jì)算加鎖后轉(zhuǎn)移到輕量鎖

處于重量鎖狀態(tài)

8.1? 8.2? 如果在Stop-The-Word期間沒有競(jìng)爭(zhēng)了低矮,就可以去膨脹(STW期間沒有其它線程獲取和釋放鎖印叁,是安全的),根據(jù)是否有hashcode,退到對(duì)應(yīng)的狀態(tài)(就是就退回使用偏向鎖 )

8.3 重量鎖期間的lock/unlock仍然處于重量鎖

計(jì)算過hashcode军掂,再加鎖和解鎖對(duì)應(yīng)狀態(tài)轉(zhuǎn)換(9.10)

在此我向大家推薦一個(gè)架構(gòu)學(xué)習(xí)交流群轮蜕。交流學(xué)習(xí)群號(hào):938837867 暗號(hào):555 里面會(huì)分享一些資深架構(gòu)師錄制的視頻錄像:有Spring,MyBatis蝗锥,Netty源碼分析跃洛,高并發(fā)、高性能终议、分布式汇竭、微服務(wù)架構(gòu)的原理葱蝗,JVM性能優(yōu)化、分布式架構(gòu)等這些成為架構(gòu)師必備

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末细燎,一起剝皮案震驚了整個(gè)濱河市两曼,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌玻驻,老刑警劉巖悼凑,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異璧瞬,居然都是意外死亡户辫,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門彪蓬,熙熙樓的掌柜王于貴愁眉苦臉地迎上來寸莫,“玉大人,你說我怎么就攤上這事档冬”炀ィ” “怎么了?”我有些...
    開封第一講書人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵酷誓,是天一觀的道長(zhǎng)披坏。 經(jīng)常有香客問我,道長(zhǎng)盐数,這世上最難降的妖魔是什么棒拂? 我笑而不...
    開封第一講書人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮玫氢,結(jié)果婚禮上帚屉,老公的妹妹穿的比我還像新娘。我一直安慰自己漾峡,他們只是感情好攻旦,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著生逸,像睡著了一般牢屋。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上槽袄,一...
    開封第一講書人閱讀 49,031評(píng)論 1 285
  • 那天烙无,我揣著相機(jī)與錄音渔呵,去河邊找鬼巷查。 笑死啤斗,一個(gè)胖子當(dāng)著我的面吹牛姥芥,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播浇冰,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼殊霞,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼畸悬!你這毒婦竟也來了歧蕉?” 一聲冷哼從身側(cè)響起灾部,我...
    開封第一講書人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎惯退,沒想到半個(gè)月后赌髓,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡催跪,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年锁蠕,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片懊蒸。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡荣倾,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出骑丸,到底是詐尸還是另有隱情舌仍,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布通危,位于F島的核電站铸豁,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏菊碟。R本人自食惡果不足惜节芥,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望逆害。 院中可真熱鬧头镊,春花似錦、人聲如沸魄幕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)梅垄。三九已至厂捞,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間队丝,已是汗流浹背靡馁。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留机久,地道東北人臭墨。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像膘盖,于是被迫代替她去往敵國(guó)和親胧弛。 傳聞我的和親對(duì)象是個(gè)殘疾皇子尤误,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容