一夜搞懂 | JVM 線程安全與鎖優(yōu)化

前言

本文已經(jīng)收錄到我的 Github 個人博客递惋,歡迎大佬們光臨寒舍:

我的 GIthub 博客

學習導圖

學習導圖

一.為什么要學習內(nèi)存模型與線程汇荐?

之前我們學習了內(nèi)存模型和線程蚊逢,了解了 JMM 和線程韭山,初步探究了 JVM 怎么實現(xiàn)并發(fā)功蜓,而本篇文章嗤堰,我們的關注點是 JVM 如何實現(xiàn)高效

并發(fā)編程的目的是為了讓程序運行得更快戴质,提高程序的響應速度度宦,雖然我們希望通過多線程執(zhí)行任務讓程序運行得更快,但是同時也會面臨非常多的挑戰(zhàn)告匠,比如像線程安全問題戈抄、線程上下文切換的問題、硬件和軟件資源限制等問題后专,這些都是并發(fā)編程給我們帶來的難題划鸽。

其中線程安全問題是我們最關心的問題之一,我們接下來主要就圍繞著線程安全的問題來展開戚哎。

image

二.核心知識點歸納

2.1 線程安全

2.1.1 定義

當多個線程訪問一個對象時裸诽,如果不用考慮這些線程在運行時環(huán)境下的調度和交替執(zhí)行,也不需要進行額外的同步型凳,或者在調用方進行任何其他的協(xié)調操作丈冬,調用這個對象的行為都可以獲得正確的結果,那這個對象是線程安全的

要求線程安全的代碼都必須具備一個特征
代碼本身封裝了所有必要的正確性保障手段(如互斥同步等)甘畅,令調用者無須關心多線程的問題埂蕊,更無須自己采取任何措施來保證多線程的正確調用。

2.1.2 分類

下面將按照線程安全的程度由強至弱分成五類

  • 不可變:外部的可見狀態(tài)永遠不會改變疏唾,在多個線程之中永遠是一致的狀態(tài)
  • 一定是線程安全的

  • 如何實現(xiàn)

    1.如果共享數(shù)據(jù)是一個基本數(shù)據(jù)類型蓄氧,只要在定義時用 final 關鍵字修飾

    2.如果共享數(shù)據(jù)是一個對象,最簡單的方法是把對象中帶有狀態(tài)的變量都聲明為 final(例如 String 類的實現(xiàn))

  • 絕對線程安全:完全滿足之前給出的線程安全的定義槐脏,即達到『不管運行時環(huán)境如何喉童,調用者都不需要任何額外的同步措施』
  • 相對線程安全:能保證對該對象單獨的操作是線程安全的,在調用時無需做額外保障措施准给,但對于一些特定順序的連續(xù)調用泄朴,可能需要在調用端使用額外的同步措施來保證調用的正確性
  • 通常意義上所講的線程安全
  • 大部分的線程安全類都屬于這種類型,如 Vector露氮、HashTable祖灰、Collections#synchronizedCollection() 包裝的集合等
  • 線程兼容:對象本身非線程安全的,但可以通過在調用端正確地使用同步手段來保證對象在并發(fā)環(huán)境中可以安全地使用
  • 是通常意義上所講的非線程安全
  • Java API 中大部分類都是屬于線程兼容的畔规,如 ArrayListHashMap
  • 線程對立:無論調用端是否采取了同步措施局扶,都無法在多線程環(huán)境中并發(fā)使用的代碼

例子:Thread 類的 suspend()resume() ,一個嘗試中斷線程叁扫,一個嘗試恢復線程三妈,在并發(fā)條件下,有可能會造成死鎖

2.1.3 實現(xiàn)

可分成兩大手段:

  • 通過代碼編寫實現(xiàn)線程安全
  • 通過虛擬機本身實現(xiàn)同步與鎖

本篇重點在虛擬機本身

1.互斥同步
  • 含義:
  • 同步:在多個線程并發(fā)訪問共享數(shù)據(jù)時莫绣,保證共享數(shù)據(jù)在同一個時刻只被一個線程使用

  • 互斥:是實現(xiàn)同步的一種手段畴蒲,臨界區(qū)、互斥量和信號量都是主要的互斥實現(xiàn)方式

  • 兩者關系:互斥是因对室,同步是果模燥;互斥是方法咖祭,同步是目的

  • 屬于悲觀并發(fā)策略悲觀鎖),即認為只要不做正確的同步措施就肯定會出現(xiàn)問題蔫骂,因此無論共享數(shù)據(jù)是否真的會出現(xiàn)競爭么翰,都要加鎖

  • 最大的問題是進行線程阻塞和喚醒所帶來的性能問題,也稱為阻塞同步

  • 使用方式:

    A.使用 synchronized 關鍵字:

  • 原理:編譯后會在同步塊的前后分別形成 monitorentermonitorexit 這兩個字節(jié)碼指令辽旋,并通過一個 reference 類型的參數(shù)來指明要鎖定和解鎖的對象

    注意:

    ? 1.若明確指定了對象參數(shù)浩嫌,則取該對象的 reference

    ? 2.否則,會根據(jù) synchronized 修飾的是實例方法還是類方法去取對應的對象實例或 Class 對象來作為鎖對象

    synchronized 處理邏輯
  • 過程:執(zhí)行 monitorenter 指令時先要嘗試獲取對象的鎖补胚。若該對象沒被鎖定或者已被當前線程獲取码耐,那么鎖計數(shù)器 + 1;而在執(zhí)行 monitorexit 指令時糖儡,鎖計數(shù)器 - 1伐坏;當鎖計數(shù)器 = 0 時怔匣,鎖就被釋放握联;若獲取對象鎖失敗,那當前線程會一直被阻塞等待每瞒,直到對象鎖被另外一個線程釋放為止

  • 特別注意

    1.synchronized 同步塊對同一條線程來說是可重入的金闽,不會出現(xiàn)自我鎖死的問題

    2.同步塊在已進入的線程執(zhí)行完之前,會阻塞后面其他線程的進入

? B.使用重入鎖 ReentrantLock

? 之前在 進階之路 | 奇妙的 Thread 之旅中也提到過重入鎖的使用剿骨,相信看過的讀者還有一些印象

  • synchronized 的相同:用法與 synchronized 很相似代芜,且都可重入

  • synchronized不同

    1.等待可中斷:當持有鎖的線程長期不釋放鎖的時候,正在等待的線程可以選擇放棄等待浓利,改為處理其他事情

    2.公平鎖:多個線程在等待同一個鎖時挤庇,必須按照申請鎖的時間順序來依次獲得鎖。而 synchronized 是非公平的贷掖,即在鎖被釋放時嫡秕,任何一個等待鎖的線程都有機會獲得鎖。ReentrantLock 默認情況下也是非公平的苹威,但可以通過帶布爾值的構造函數(shù)改用公平鎖

    3.鎖綁定多個條件:一個 ReentrantLock 對象可以通過多次調用 newCondition() 同時綁定多個 Condition 對象昆咽。而在 synchronized 中,鎖對象的 wait()notify()notifyAl() 只能實現(xiàn)一個隱含的條件牙甫,若要和多于一個的條件關聯(lián)不得不額外地添加一個鎖

  • 選擇:在 synchronized 能實現(xiàn)需求的情況下掷酗,優(yōu)先考慮使用它來進行同步。理由如下:
  • synchronizedJava語法層面的同步窟哺,足夠清晰簡單
  • Lock 必須由程序員確保在 finally 塊中釋放鎖泻轰,而 synchronized 可以由 JVM 確保鎖的自動釋放
2.非阻塞同步
  • 定義:基于沖突檢測的樂觀并發(fā)策略(樂觀鎖),即先進行操作且轨,若無其他線程爭用共享數(shù)據(jù)浮声,操作成功亩鬼;反之產(chǎn)生了沖突再去采取其他的補償措施
  • 為了保證操作沖突檢測這兩步具備原子性,需要用到硬件指令集阿蝶,比如:
  • 測試并設置
  • 獲取并增加
  • 交換
  • 比較并交換CAS
  • 加載鏈接 / 條件存儲
3.無同步方案
  • 定義:不用同步的方式保證線程安全雳锋,因為有些代碼天生就是線程安全的。
  • 例子:

A.可重入代碼/ 純代碼

  • 含義:可在代碼執(zhí)行的任何時刻中斷它去執(zhí)行另外一段代碼羡洁,當控制權返回后原來的程序并不會出現(xiàn)任何錯誤
  • 共同特征:不依賴存儲在堆上的數(shù)據(jù)和公用的系統(tǒng)資源玷过、用到的狀態(tài)量都由參數(shù)中傳入、不調用非可重入的方法
  • 判定依據(jù):如果一個方法筑煮,它的返回結果是可預測的辛蚊,只要輸入相同的數(shù)據(jù)就都能返回相同的結果,就滿足可重入性
  • 注意:滿足可重入性的代碼一定是線程安全的真仲,反之袋马,滿足線程安全的代碼不一定是可重入的

B.線程本地存儲

  • 含義:把共享數(shù)據(jù)的可見范圍限制在同一個線程之內(nèi),無須同步就能保證線程之間不出現(xiàn)數(shù)據(jù)爭用的問題
  • 想詳細了解 ThreadLocal 的讀者秸应,可以看下筆者之前寫的一篇文章:進階之路 | 奇妙的 Handler 之旅

2.2 鎖優(yōu)化

一圖帶你看遍鎖

解決并發(fā)的正確性之后虑凛,為了能在線程之間更『高效』地共享數(shù)據(jù)、解決競爭問題软啼、提高程序的執(zhí)行效率桑谍,下面介紹五種鎖優(yōu)化技術

2.2.1 適應性自旋

  • 背景:互斥同步在實現(xiàn)阻塞和喚醒時需要掛起線程和恢復線程的操作,都需要轉入內(nèi)核態(tài)中完成祸挪,很影響系統(tǒng)的并發(fā)性能锣披;同時,在許多應用上共享數(shù)據(jù)的鎖定狀態(tài)只是暫時贿条,沒必要去掛起和恢復線程
  • 自旋鎖:當物理機器有多個處理器使得多個線程同時并行執(zhí)行時雹仿,先讓后請求鎖的線程等待,但不放棄處理器的執(zhí)行時間整以,看看持有鎖的線程是否很快就會釋放鎖胧辽,這時只需讓線程執(zhí)行一個忙循環(huán),即自旋

注意:自旋等待不能代替阻塞悄蕾,它雖然能避免線程切換的開銷票顾,但會占用處理器時間,因此自旋等待的時間必須要有一定的限度帆调,如果自旋超過了限定的次數(shù)(默認10次)仍未成功獲鎖奠骄,就需要掛線程了

  • 自適應自旋鎖:自旋的時間不再固定,而是由該鎖上的上次自旋時間及鎖的擁有者的狀態(tài)共同決定番刊。具體表現(xiàn)是:
  • 如果對于某個鎖含鳞,自旋等待剛剛成功獲得,且持有鎖的線程正在運行中芹务,那么虛擬機很可能允許自旋等待的時間更久點
  • 如果對于某個鎖蝉绷,自旋很少成功獲得過鸭廷,那么很可能以后將省略自旋等待這個鎖,避免浪費處理器資源

2.2.2 鎖消除

  • 定義:指虛擬機即時編譯器在運行時熔吗,對一些代碼上要求同步辆床,但是被檢測到不可能存在共享數(shù)據(jù)競爭的鎖進行消除
  • 判定依據(jù):如果一段代碼中上的所有數(shù)據(jù)都不會逃逸出去被其他線程訪問到,可把它們當做上數(shù)據(jù)對待桅狠,即線程私有的讼载,無須同步加鎖

2.2.3 鎖粗化

  • 一般情況下,會將同步塊的作用范圍限制到只在共享數(shù)據(jù)的實際作用域中才進行同步中跌,使得需要同步的操作數(shù)量盡可能變小咨堤,保證就算存在鎖競爭,等待鎖的線程也能盡快拿到鎖
  • 但如果反復操作對同一個對象進行加鎖和解鎖漩符,即使沒有線程競爭一喘,頻繁地進行互斥同步操作也會導致不必要的性能損耗,此時嗜暴,虛擬機將會把加鎖同步的范圍粗化到整個操作序列的外部凸克,這樣只需加一次鎖

2.2.4 輕量級鎖

  • 目的:在沒有多線程競爭的前提下減少傳統(tǒng)的重量級鎖使用操作系統(tǒng)互斥量產(chǎn)生的性能消耗灼伤,注意不是用來代替重量級鎖的

首先先理解 HotSpot 虛擬機的對象頭的內(nèi)存布局:分為兩部分

  • 第一部分用于存儲對象自身的運行時數(shù)據(jù)触徐,這部分被稱為 Mark Word咪鲜,是實現(xiàn)輕量級鎖和偏向鎖的關鍵狐赡。如哈希碼、GC 分代年齡等
  • 另外一部分用于存儲指向方法區(qū)對象類型數(shù)據(jù)的指針疟丙,如果是數(shù)組對象還會有一個額外的部分用于存儲數(shù)組長度
Mark Word 結構
  • 加鎖過程:

    1.代碼進入同步塊時颖侄,如果同步對象未被鎖定(鎖標志位為 01),虛擬機會在當前線程的棧幀中建立一個名為 Lock Record 的空間享郊,用于存儲鎖對象 Mark Word 的拷貝览祖。如下圖

image

2.之后虛擬機會嘗試用 CAS 操作將對象的 Mark Word 更新為指向 Lock Record 的指針。若更新動作成功炊琉,那么當前線程就擁有了該對象的鎖展蒂,且對象 Mark Word 的鎖標志位變?yōu)?00,即處于輕量級鎖定狀態(tài)苔咪;反之锰悼,虛擬機會先檢查對象的 Mark Word 是否指向當前線程的棧幀,若是团赏,則當前線程已有該對象的鎖箕般,可直接進入同步塊繼續(xù)執(zhí)行,否則說明改對象已被其他線程搶占舔清。如下圖:

CAS后堆棧與對象的狀態(tài)

另外丝里,如果有兩條以上的線程爭用同一個鎖曲初,那輕量級鎖就不再有效,要膨脹為重量級鎖杯聚,鎖標志位變?yōu)?10臼婆,Mark Word 中存儲的就是指向重量級鎖的指針,后面等待鎖的線程也要進入阻塞狀態(tài)

加鎖流程
  • 解鎖過程:若對象的 Mark Word 仍指向著線程的 Lock Record幌绍,就用 CAS 操作把對象當前的 Mark Word 和線程中復制的 Displaced Mark Word 替換回來目锭。若替換成功,那么就完成了整個同步過程纷捞;反之痢虹,說明有其他線程嘗試獲取該鎖,那么就要在釋放鎖的同時喚醒被掛起的線程

    解鎖過程
  • 優(yōu)點:因為對于絕大部分的鎖主儡,在整個同步周期內(nèi)都是不存在競爭的奖唯,所以輕量級鎖通過使用 CAS 操作消除同步使用的互斥量

  • 自旋鎖和輕量級鎖的關系:

  • 自旋鎖是為了減少線程掛起次數(shù)
  • 輕量級鎖是在加鎖的時候,如何使用一種更高效的方式來加鎖

Q:處于輕量級鎖狀態(tài)時糜值,會不會使用自旋鎖這個競爭機制

A:線程首先會通過 CAS 獲取鎖丰捷,失敗后通過自旋鎖來嘗試獲取鎖,再失敗鎖就膨脹為重量級鎖寂汇。所以輕量級鎖狀態(tài)下可能會有自旋鎖的參與(CAS 將對象頭的標記指向鎖記錄指針失敗的時候)

2.2.5 偏向鎖

  • 目的:消除數(shù)據(jù)在無競爭情況下的同步原語病往,進一步提高程序的運行性能
  • 如果說輕量級鎖是在無競爭的情況下使用 CAS消除同步使用的互斥量

  • 偏向鎖就是在無競爭情況下把整個同步都消除掉

  • 含義:偏向鎖會偏向于第一個獲得它的線程,如果在后面的執(zhí)行中該鎖沒有被其他的線程獲取骄瓣,則持有偏向鎖的線程將永遠不需要再進行同步

  • 加鎖過程:啟用偏向鎖的鎖對象在第一次被線程獲取時停巷,Mark Word 的鎖標志位會被設置為 01,即偏向模式榕栏,同時使用 CAS 操作把獲取到這個鎖的線程 ID 記錄在對象的 Mark Word 中畔勤。若操作成功,持有偏向鎖的線程以后每次進入這個鎖相關的同步塊時都可不再進行任何同步操作

  • 解鎖過程:當有另外的線程去嘗試獲取這個鎖時扒磁,根據(jù)鎖對象目前是否處于被鎖定的狀態(tài)庆揪,撤銷偏向后恢復到未鎖定 01 或輕量級鎖定 00 的狀態(tài),后續(xù)的同步操作就如輕量級鎖執(zhí)行過程妨托。如下圖:

    image
  • 優(yōu)點:可提高帶有同步但無競爭的程序性能缸榛,但若程序中大多數(shù)鎖總被多個線程訪問,此模式就沒必要了

解放啦

三.碎碎念

能夠寫出高性能兰伤、高伸縮性的并發(fā)程序是一門藝術内颗,而了解并發(fā)在底層是如何實現(xiàn)的,則是掌握這門藝術的前提医清,也是成長為高級程序員的必備知識弯院!

加油吧历葛!騷年酬屉!以夢為馬,不負韶華筒捺!

沖鴨

如果文章對您有一點幫助的話,希望您能點一下贊纸厉,您的點贊系吭,是我前進的動力

本文參考鏈接:

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末躯枢,一起剝皮案震驚了整個濱河市则吟,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌锄蹂,老刑警劉巖氓仲,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異得糜,居然都是意外死亡敬扛,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進店門朝抖,熙熙樓的掌柜王于貴愁眉苦臉地迎上來啥箭,“玉大人,你說我怎么就攤上這事治宣〖苯模” “怎么了?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵炼七,是天一觀的道長缆巧。 經(jīng)常有香客問我,道長豌拙,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任题暖,我火速辦了婚禮按傅,結果婚禮上,老公的妹妹穿的比我還像新娘胧卤。我一直安慰自己唯绍,他們只是感情好,可當我...
    茶點故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布枝誊。 她就那樣靜靜地躺著况芒,像睡著了一般。 火紅的嫁衣襯著肌膚如雪叶撒。 梳的紋絲不亂的頭發(fā)上绝骚,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天耐版,我揣著相機與錄音,去河邊找鬼压汪。 笑死粪牲,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的止剖。 我是一名探鬼主播腺阳,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼穿香!你這毒婦竟也來了亭引?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤皮获,失蹤者是張志新(化名)和其女友劉穎痛侍,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體魔市,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡主届,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了待德。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片君丁。...
    茶點故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖将宪,靈堂內(nèi)的尸體忽然破棺而出绘闷,到底是詐尸還是另有隱情,我是刑警寧澤较坛,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布印蔗,位于F島的核電站,受9級特大地震影響丑勤,放射性物質發(fā)生泄漏华嘹。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一法竞、第九天 我趴在偏房一處隱蔽的房頂上張望耙厚。 院中可真熱鬧,春花似錦岔霸、人聲如沸薛躬。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽型宝。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間趴酣,已是汗流浹背梨树。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留价卤,地道東北人劝萤。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像慎璧,于是被迫代替她去往敵國和親床嫌。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,792評論 2 345

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