Java的鎖機制--偏向鎖、輕量鎖渐尿、自旋鎖醉途、重量鎖

基礎(chǔ)知識

線程切換代價

Java的線程是映射到操作系統(tǒng)的原生線程之上的,如果阻塞或喚醒一個線程就需要操作系統(tǒng)介入砖茸,需要在用戶態(tài)和內(nèi)核態(tài)之間切換隘擎,該切換會消耗大量的系統(tǒng)資源,因為用戶態(tài)和內(nèi)核態(tài)均有各自專用的內(nèi)存空間凉夯,專用的寄存器等货葬,用戶態(tài)切換至內(nèi)核態(tài)需要傳遞很多變量、參數(shù)給內(nèi)核劲够,內(nèi)核也需要保護好用戶態(tài)切換時的一些寄存器值震桶、變量等,以便內(nèi)核態(tài)調(diào)用結(jié)束后切換回用戶態(tài)繼續(xù)工作征绎。

JVM1.6之前蹲姐,Synchronized會導致爭不到鎖的線程直接進入阻塞狀態(tài),所以說其是一個重量級的同步操作,被稱為重量鎖柴墩。

為了緩解上述的性能問題忙厌,JVM1.6開始,引入了偏向鎖江咳、輕量鎖逢净,其均屬于樂觀鎖。

Mark Word

在JVM 1.6中歼指,對象實例在堆內(nèi)存中被分為3部分: 對象頭爹土、實例數(shù)據(jù)、對齊填充踩身。

對象頭的組成部分: Mark Word着饥、指向類的指針、數(shù)組長度(可選惰赋,數(shù)組類型時才有)宰掉,每個部分長度均為1個字寬,32位的JVM中赁濒,1字寬位32 bit轨奄,64位的JVM中,1字寬為64 bit拒炎。

鎖升級功能主要依賴Mark Word中鎖標志位和是否偏向鎖標志位挪拟。

Synchronized同步鎖的升級優(yōu)化路徑: 偏向鎖->輕量級鎖->重量級鎖。

2.jpg

鎖升級

偏向鎖

偏向鎖主要用來優(yōu)化同一線程多次申請同一個鎖的競爭击你,在某些情況下玉组,大部分時間都是同一個線程競爭鎖資源。

當線程1再次獲取鎖時丁侄,會比較當前線程ID與鎖對象頭Mark Word中的線程ID是否一致惯雳。

  • 如果一致,直接獲取鎖鸿摇,無需CAS來搶占鎖石景;
  • 如果不一致,需要查看鎖對象頭Mark Word中的線程是否存活:
    • 若存活拙吉,查找線程1的棧幀信息潮孽,如果線程1還需要繼續(xù)持有該鎖對象,那么暫停線程1(Stop-The-World)筷黔,撤銷偏向鎖往史,升級為輕量級鎖;如果線程1不再使用鎖對象佛舱,則將鎖對象設(shè)置為無鎖狀態(tài)(也屬于鎖撤銷)椎例,然后重新偏向線程2揽乱;
    • 若不存活,則將鎖對象設(shè)置為無鎖狀態(tài)(也屬于鎖撤銷)粟矿,然后重新偏向線程2。

可以看到损拢,當持有鎖的線程宕掉之后陌粹,其他請求鎖的線程會檢查持有鎖的線程是否存活,若不存活則直接撤銷鎖福压,從而避免了死鎖掏秩。

在高并發(fā)場景下,當大量線程同時競爭同一個鎖資源時荆姆,偏向鎖會被撤銷蒙幻,發(fā)生STW,加大性能開銷胆筒。

JVM的默認配置為: -XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=4000邮破,即默認開啟偏向鎖,并且延遲4秒生效仆救,之所以延遲抒和,是因為JVM剛啟動時競爭比較激烈。

關(guān)閉偏向鎖: -XX:-UseBiasedLocking彤蔽,也可以直接設(shè)置為重量級鎖: -XX:+UseHeavyMonitors摧莽。

輕量鎖

輕量鎖適應的場景是: 各線程交替執(zhí)行同步塊,大部分的鎖在同步周期內(nèi)不存在長時間的競爭顿痪。

輕量鎖在虛擬機內(nèi)部是通過BasicObjectLock對象實現(xiàn)的镊辕,該對象內(nèi)部由一個BasicLock對象_lock和一個鎖對象指針_obj組成,BasicObjectLock對象放置在Java棧幀中蚁袭。

在BasicLock內(nèi)部還維護著displace_header字段征懈,用于備份鎖對象頭部的Mark Word。

// A BasicObjectLock associates a specific Java object with a BasicLock.
// It is currently embedded in an interpreter frame.
class BasicObjectLock VALUE_OBJ_CLASS_SPEC {
 private:
  BasicLock _lock;                        // the lock, must be double word aligned
  oop       _obj;                         // object holds the lock;
};
class BasicLock VALUE_OBJ_CLASS_SPEC {
 private:
  volatile markOop _displaced_header;
};
1.jpg

當需要判斷一個線程是否持有該鎖對象時揩悄,只需要簡單判斷鎖對象頭的指針是否在當前線程棧地址范圍即可受裹。

加鎖

線程在執(zhí)行同步塊之前,JVM會先在當前線程的棧幀中創(chuàng)建用于存儲鎖記錄的空間虏束,即BasicObjectLock對象棉饶。

創(chuàng)建過程如下:

  • 將鎖對象頭的Mark Word拷貝賦值給BasicObjectLock中BasicLock對象的_displaced_header字段;
  • 然后線程嘗試使用CAS將鎖對象頭的Mark Word替換為指向該BasicObjectLock對象的指針镇匀;
  • 若成功照藻,則當前線程獲得鎖。若失敗汗侵,表示其他線程競爭鎖幸缕,當前線程嘗試使用自旋來獲取鎖群发。

可以看到,當前線程即時獲取鎖失敗发乔,也不會立即阻塞掛起熟妓,而是先嘗試使用自旋來獲取鎖。如果競爭不是很激烈栏尚,可能幾次自旋起愈,當前線程就獲取到鎖了,從而避免了1次線程的上下文切換译仗。

若當前線程自旋獲取鎖失敗抬虽,鎖就會膨脹成重量鎖,當前線程阻塞掛起纵菌。

解鎖

輕量鎖解鎖時阐污,會使用原子的CAS操作將BasicObjectLock對象備份的_displaced_header替換回到鎖對象頭的Mark Word。若成功咱圆,則表明沒有競爭發(fā)生笛辟,解鎖成功;若失敗序苏,則表明當前鎖存在競爭(此時鎖已經(jīng)膨脹為重量鎖)隘膘,釋放鎖并喚醒阻塞的線程。

自旋鎖

當鎖處于輕量鎖狀態(tài)杠览,且被某線程持有時弯菊,其他線程嘗試獲取鎖失敗后,不會直接阻塞掛起踱阿,而是先自旋一定次數(shù)管钳,避免正在持有鎖的線程可能在很短的時間內(nèi)釋放鎖資源。

從JVM 1.7開始软舌,自旋鎖默認啟用才漆,自旋次數(shù)不宜設(shè)置過大(避免長時間占用CPU),-XX:+UseSpinning -XX:PreBlockSpin=10佛点,JVM 1.7之后醇滥,默認的自旋次數(shù)由JVM根據(jù)實際系統(tǒng)環(huán)境靈活設(shè)置。

在鎖競爭不是很激烈且鎖占用的時間非常短的場景下超营,自旋鎖可以通過減少上下文切換來提高系統(tǒng)性能鸳玩;在鎖競爭激烈或者鎖占用時間較長的場景下,自旋鎖會導致大量的線程一直處于CAS重試狀態(tài)演闭,造成CPU空轉(zhuǎn)不跟。

在高并發(fā)場景下,可以通過關(guān)閉自旋鎖來優(yōu)化系統(tǒng)性能: -XX:-UseSpinning米碰。

該線程自旋之后仍舊未獲取鎖窝革,則其會將鎖對象升級為重量鎖购城。未搶到鎖的線程都會進入Monitor,之后會被阻塞到WaitSet中虐译。

這里有個問題瘪板,假設(shè)持有輕量鎖的線程執(zhí)行同步塊的時候宕掉了,則不會有釋放鎖并喚醒阻塞線程的動作漆诽,此時會造成死鎖嗎侮攀?

答案是肯定不會的,因為其他線程請求鎖時拴泌,會有1個線程自旋獲取鎖失敗后將鎖升級為重量鎖,然后獲取重量鎖的線程在釋放的時候會將WaitSet中阻塞的線程喚醒惊橱,也就是說即使持有輕量級鎖的線程不喚醒阻塞線程蚪腐,其他持有重量級鎖的線程在釋放鎖的時候也會喚醒WaitSet中的阻塞線程的,可謂是雙重保障税朴,哈哈回季。

重量鎖

當多個線程同時請求某個Monitor時,對象的Monitor會設(shè)置以下狀態(tài)用來區(qū)分請求的線程:

  • Contention List: 所有請求鎖的線程被首先放置到該競爭隊列正林;
  • Entry List: Contention List中那些有資格成為候選人的線程會被轉(zhuǎn)移到Entry List泡一;
  • Wait Set: 調(diào)用Wait方法等被阻塞的線程會被放置到Wait Set;
  • OnDeck: 任何時刻僅能有1個線程競爭鎖觅廓,該線程稱為OnDeck鼻忠;
  • Owner: 獲取鎖的線程稱為Owner;
  • !Owner: 釋放鎖的線程杈绸。
4.jpg

EntryList與ContentionList邏輯上同屬等待隊列帖蔓,ContentionList會被線程并發(fā)訪問,為了降低對ContentionList隊尾的爭用瞳脓,而建立EntryList塑娇。Owner線程在unlock時會從ContentionList中遷移線程到EntryList,并會指定EntryList中的某個線程(一般為Head)為Ready(OnDeck)線程劫侧。Owner線程并不是把鎖傳遞給OnDeck線程埋酬,只是把競爭鎖的權(quán)利交給OnDeck,OnDeck線程需要重新競爭鎖烧栋。這樣做雖然犧牲了一定的公平性写妥,但極大的提高了整體吞吐量,在 Hotspot中把OnDeck的選擇行為稱之為“競爭切換”审姓。

OnDeck線程獲得鎖后即變?yōu)镺wner線程耳标,無法獲得鎖則會依然留在EntryList中,考慮到公平性邑跪,在EntryList中的位置不發(fā)生變化(依然在隊頭)次坡。如果Owner線程被wait方法阻塞呼猪,則轉(zhuǎn)移到WaitSet隊列;如果在某個時刻被notify/notifyAll喚醒砸琅,則再次轉(zhuǎn)移到EntryList宋距。

需要注意的是:當持有重量鎖的線程在運行期間出錯,會自動釋放掉鎖症脂,從而避免死鎖谚赎。

小結(jié)

鎖升級的過程可以通過下面2張圖來展示:

3.jpg
5.jpg

參考

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市诱篷,隨后出現(xiàn)的幾起案子壶唤,更是在濱河造成了極大的恐慌,老刑警劉巖棕所,帶你破解...
    沈念sama閱讀 211,948評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件闸盔,死亡現(xiàn)場離奇詭異,居然都是意外死亡琳省,警方通過查閱死者的電腦和手機迎吵,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,371評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來针贬,“玉大人击费,你說我怎么就攤上這事¤胨” “怎么了蔫巩?”我有些...
    開封第一講書人閱讀 157,490評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長快压。 經(jīng)常有香客問我批幌,道長,這世上最難降的妖魔是什么嗓节? 我笑而不...
    開封第一講書人閱讀 56,521評論 1 284
  • 正文 為了忘掉前任荧缘,我火速辦了婚禮,結(jié)果婚禮上拦宣,老公的妹妹穿的比我還像新娘截粗。我一直安慰自己,他們只是感情好鸵隧,可當我...
    茶點故事閱讀 65,627評論 6 386
  • 文/花漫 我一把揭開白布绸罗。 她就那樣靜靜地躺著,像睡著了一般豆瘫。 火紅的嫁衣襯著肌膚如雪珊蟀。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,842評論 1 290
  • 那天,我揣著相機與錄音育灸,去河邊找鬼腻窒。 笑死,一個胖子當著我的面吹牛磅崭,可吹牛的內(nèi)容都是我干的儿子。 我是一名探鬼主播,決...
    沈念sama閱讀 38,997評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼砸喻,長吁一口氣:“原來是場噩夢啊……” “哼柔逼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起割岛,我...
    開封第一講書人閱讀 37,741評論 0 268
  • 序言:老撾萬榮一對情侶失蹤愉适,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后癣漆,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體维咸,經(jīng)...
    沈念sama閱讀 44,203評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,534評論 2 327
  • 正文 我和宋清朗相戀三年扑媚,在試婚紗的時候發(fā)現(xiàn)自己被綠了腰湾。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片雷恃。...
    茶點故事閱讀 38,673評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡疆股,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出倒槐,到底是詐尸還是另有隱情旬痹,我是刑警寧澤,帶...
    沈念sama閱讀 34,339評論 4 330
  • 正文 年R本政府宣布讨越,位于F島的核電站两残,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏把跨。R本人自食惡果不足惜人弓,卻給世界環(huán)境...
    茶點故事閱讀 39,955評論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望着逐。 院中可真熱鬧崔赌,春花似錦、人聲如沸耸别。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,770評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽秀姐。三九已至慈迈,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間省有,已是汗流浹背痒留。 一陣腳步聲響...
    開封第一講書人閱讀 32,000評論 1 266
  • 我被黑心中介騙來泰國打工谴麦, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人狭瞎。 一個月前我還...
    沈念sama閱讀 46,394評論 2 360
  • 正文 我出身青樓细移,卻偏偏與公主長得像,于是被迫代替她去往敵國和親熊锭。 傳聞我的和親對象是個殘疾皇子弧轧,可洞房花燭夜當晚...
    茶點故事閱讀 43,562評論 2 349