Java 線程優(yōu)化 偏向鎖订讼,輕量級鎖髓窜、重量級鎖課程

Java 6 對 synchronized 鎖做了多方面的優(yōu)化,其中最主要的就是引入了 偏向鎖和輕量級鎖欺殿。鎖的獲取次序依次是?偏向鎖?->?輕量級鎖?-> 重量級鎖寄纵。

synchronized 實現(xiàn)原理

要了解 synchronized 的原理需要先理清楚兩件事情:對象頭和 Monitor。

對象頭

Java對象在內存中分為三部分:對象頭脖苏、實例數(shù)據(jù)程拭、對齊填充。當我們在 Java 代碼中棍潘,使用 new 創(chuàng)建一個對象的時候恃鞋,JVM 會在堆中創(chuàng)建一個 instanceOopDesc 對象,這個對象中包含了對象頭以及實例數(shù)據(jù)亦歉。

instanceOopDesc 的基類為 oopDesc 類恤浪。它的結構如下:


oopDesc 結構

其中 _mark 和 _metadata 一起組成了對象頭。_metadata 主要保存了類元數(shù)據(jù)肴楷。重點看下 _mark 屬性水由,_mark 是 markOop 類型數(shù)據(jù),一般稱它為標記字段(Mark Word)赛蔫,其中主要存儲了對象的 hashCode砂客、分代年齡泥张、鎖標志位,是否偏向鎖等鞠值。

用一張圖來表示 32 位 Java 虛擬機的 Mark Word 的默認存儲結構如下:


32 位 Java 虛擬機的 Mark Word 的默認存儲結構??

默認情況下媚创,沒有線程進行加鎖操作,所以鎖對象中的 Mark Word 處于無鎖狀態(tài)齿诉。但是考慮到 JVM 的空間效率筝野,Mark Word 被設計成為一個非固定的數(shù)據(jù)結構,以便存儲更多的有效數(shù)據(jù)粤剧,它會根據(jù)對象本身的狀態(tài)復用自己的存儲空間,如 32 位 JVM 下挥唠,除了上述列出的 Mark Word 默認存儲結構外抵恋,還有如下可能變化的結構:


不同情況下Mark Word 的結構

從圖中可以看出,根據(jù)"鎖標志位”以及"是否為偏向鎖"宝磨,Java 中的鎖可以分為以下幾種狀態(tài):


Java 中的鎖的幾種狀態(tài)

在 Java 6 之前弧关,并沒有輕量級鎖和偏向鎖,只有重量級鎖唤锉,也就是通常所說 synchronized 的對象鎖世囊,鎖標志位為 10。從圖中的描述可以看出:當鎖是重量級鎖時窿祥,對象頭中 Mark Word 會用 30 bit 來指向一個“互斥量”株憾,而這個互斥量就是 Monitor。

Monitor

Monitor 可以把它理解為一個同步工具晒衩,也可以描述為一種同步機制嗤瞎。實際上,它是一個保存在對象頭中的一個對象听系。在 markOop 中有如下代碼:

通過 monitor() 方法創(chuàng)建一個 ObjectMonitor 對象贝奇,而 ObjectMonitor 就是 Java 虛擬機中的 Monitor 的具體實現(xiàn)。因此 Java 中每個對象都會有一個對應的 ObjectMonitor 對象靠胜,這也是 Java 中所有的 Object 都可以作為鎖對象的原因掉瞳。

ObjectMonitor 是如何實現(xiàn)同步機制

首先看下 ObjectMonitor 的結構:

ObjectMonitor 結構

其中有幾個比較關鍵的屬性:

當多個線程同時訪問一段同步代碼時,首先會進入 _EntryList 隊列中浪漠,當某個線程通過競爭獲取到對象的 monitor 后陕习,monitor 會把 _owner 變量設置為當前線程,同時 monitor 中的計數(shù)器 _count 加 1郑藏,即獲得對象鎖衡查。

若持有 monitor 的線程調用 wait() 方法,將釋放當前持有的 monitor必盖,_owner 變量恢復為 null拌牲, _count 自減 1俱饿,同時該線程進入 _WaitSet 集合中等待被喚醒。若當前線程執(zhí)行完畢也將釋放 monitor(鎖)并復位變量的值塌忽,以便其他線程進入獲取 monitor(鎖)拍埠。

實例演示

比如以下代碼通過 3 個線程分別執(zhí)行以下同步代碼塊:

鎖對象是 lock 對象,在 JVM 中會有一個 ObjectMonitor 對象與之對應土居。如下圖所示:

分別使用 3 個線程來執(zhí)行以上同步代碼塊枣购。默認情況下,3 個線程都會先進入 ObjectMonitor 中的 EntrySet 隊列中擦耀,如下所示:

假設線程 2 首先通過競爭獲取到了鎖對象棉圈,則 ObjectMonitor 中的 Owner 指向線程 2,并將 count 加 1眷蜓。結果如下:

上圖中 Owner 指向線程 2 表示它已經成功獲取到鎖(Monitor)對象分瘾,其他線程只能處于阻塞(blocking)狀態(tài)。如果線程 2 在執(zhí)行過程中調用 wait() 操作吁系,則線程 2 會釋放鎖(Monitor)對象德召,以便其他線程進入獲取鎖(Monitor)對象,Owner 變量恢復為 null汽纤,count 做減 1 操作上岗,同時線程 2 會添加到 WaitSet 集合,進入等待(waiting)狀態(tài)并等待被喚醒蕴坪。結果如下:

然后線程 1 和線程 3 再次通過競爭獲取到鎖(Monitor)對象肴掷,則重新將 Owner 指向成功獲取到鎖的線程。假設線程 1 獲取到鎖辞嗡,如下:

當線程 1 中的代碼執(zhí)行完畢以后捆等,同樣會自動釋放鎖,以便其他線程再次獲取鎖對象续室。

實際上栋烤,ObjectMonitor 的同步機制是 JVM 對操作系統(tǒng)級別的 Mutex Lock(互斥鎖)的管理過程,其間都會轉入操作系統(tǒng)內核態(tài)挺狰。也就是說 synchronized 實現(xiàn)鎖明郭,在“重量級鎖”狀態(tài)下,當多個線程之間切換上下文時丰泊,還是一個比較重量級的操作薯定。


Java 虛擬機對 synchronized 的優(yōu)化

從 Java 6 開始,虛擬機對 synchronized 關鍵字做了多方面的優(yōu)化瞳购,主要目的就是话侄,避免 ObjectMonitor 的訪問,減少“重量級鎖”的使用次數(shù),并最終減少線程上下文切換的頻率 年堆。其中主要做了以下幾個優(yōu)化: 鎖自旋吞杭、輕量級鎖、偏向鎖变丧。

鎖自旋

線程的阻塞和喚醒需要 CPU 從用戶態(tài)轉為核心態(tài)芽狗,頻繁的阻塞和喚醒對 CPU 來說是一件負擔很重的工作,勢必會給系統(tǒng)的并發(fā)性能帶來很大的壓力痒蓬,所以 Java 引入了自旋鎖的操作童擎。實際上自旋鎖在 Java 1.4 就被引入了,默認關閉攻晒,但是可以使用參數(shù) -XX:+UseSpinning 將其開啟顾复。但是從 Java 6 之后默認開啟。

所謂自旋炎辨,就是讓該線程等待一段時間捕透,不會被立即掛起,看當前持有鎖的線程是否會很快釋放鎖碴萧。而所謂的等待就是執(zhí)行一段無意義的循環(huán)即可(自旋)。

自旋鎖也存在一定的缺陷:自旋鎖要占用 CPU末购,如果鎖競爭的時間比較長破喻,那么自旋通常不能獲得鎖,白白浪費了自旋占用的 CPU 時間盟榴。這通常發(fā)生在鎖持有時間長曹质,且競爭激烈的場景中,此時應主動禁用自旋鎖擎场。


輕量級鎖

有時候 Java 虛擬機中會存在這種情形:對于一塊同步代碼羽德,雖然有多個不同線程會去執(zhí)行,但是這些線程是在不同的時間段交替請求這把鎖對象迅办,也就是不存在鎖競爭的情況宅静。在這種情況下,鎖會保持在輕量級鎖的狀態(tài)站欺,從而避免重量級鎖的阻塞和喚醒操作姨夹。

要了解輕量級鎖的工作流程,還是需要再次看下對象頭中的 Mark Word矾策。上文中已經提到磷账,鎖的標志位包含幾種情況:00 代表輕量級鎖、01 代表無鎖(或者偏向鎖)贾虽、10 代表重量級鎖逃糟、11 則跟垃圾回收算法的標記有關。

當線程執(zhí)行某同步代碼時,Java 虛擬機會在當前線程的棧幀中開辟一塊空間(Lock Record)作為該鎖的記錄绰咽,如下圖所示:

然后 Java 虛擬機會嘗試使用 CAS(Compare And Swap)操作菇肃,將鎖對象的 Mark Word 拷貝到這塊空間中,并且將鎖記錄中的 owner 指向 Mark Word剃诅。結果如下:

當線程再次執(zhí)行此同步代碼塊時巷送,判斷當前對象的 Mark Word 是否指向當前線程的棧幀,如果是則表示當前線程已經持有當前對象的鎖矛辕,則直接執(zhí)行同步代碼塊笑跛;否則只能說明該鎖對象已經被其他線程搶占了,這時輕量級鎖需要膨脹為重量級鎖聊品。

輕量級鎖所適應的場景是線程交替執(zhí)行同步塊的場合飞蹂,如果存在同一時間訪問同一鎖的場合,就會導致輕量級鎖膨脹為重量級鎖翻屈。


偏向鎖

輕量級鎖是在沒有鎖競爭情況下的鎖狀態(tài)陈哑,但是在有些時候鎖不僅存在多線程的競爭,而且總是由同一個線程獲得伸眶。因此為了讓線程獲得鎖的代價更低引入了偏向鎖的概念惊窖。偏向鎖的意思是如果一個線程獲得了一個偏向鎖,如果在接下來的一段時間中沒有其他線程來競爭鎖厘贼,那么持有偏向鎖的線程再次進入或者退出同一個同步代碼塊界酒,不需要再次進行搶占鎖和釋放鎖的操作。偏向鎖可以通過 -XX:+UseBiasedLocking 開啟或者關閉嘴秸。

偏向鎖的具體實現(xiàn)就是在鎖對象的對象頭中有個 ThreadId 字段毁欣,默認情況下這個字段是空的,當?shù)谝淮潍@取鎖的時候岳掐,就將自身的 ThreadId 寫入鎖對象的 Mark Word 中的 ThreadId 字段內凭疮,將是否偏向鎖的狀態(tài)置為 01。這樣下次獲取鎖的時候串述,直接檢查 ThreadId 是否和自身線程 Id 一致执解,如果一致,則認為當前線程已經獲取了鎖剖煌,因此不需再次獲取鎖材鹦,略過了輕量級鎖和重量級鎖的加鎖階段。提高了效率耕姊。

其實偏向鎖并不適合所有應用場景, 因為一旦出現(xiàn)鎖競爭桶唐,偏向鎖會被撤銷,并膨脹成輕量級鎖茉兰,而撤銷操作(revoke)是比較重的行為尤泽,只有當存在較多不會真正競爭的 synchronized 塊時,才能體現(xiàn)出明顯改善;因此實踐中坯约,還是需要考慮具體業(yè)務場景熊咽,并測試后,再決定是否開啟/關閉偏向鎖闹丐。

最后

本課程主要介紹了 Java 中鎖的幾種狀態(tài)横殴,其中偏向鎖和輕量級鎖都是通過自旋等技術避免真正的加鎖,而重量級鎖才是獲取鎖和釋放鎖卿拴,重量級鎖通過對象內部的監(jiān)視器(ObjectMonitor)實現(xiàn)衫仑,其本質是依賴于底層操作系統(tǒng)的 Mutex Lock 實現(xiàn),操作系統(tǒng)實現(xiàn)線程之間的切換需要從用戶態(tài)到內核態(tài)的切換堕花,成本非常高文狱。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市缘挽,隨后出現(xiàn)的幾起案子瞄崇,更是在濱河造成了極大的恐慌,老刑警劉巖壕曼,帶你破解...
    沈念sama閱讀 218,122評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件苏研,死亡現(xiàn)場離奇詭異,居然都是意外死亡腮郊,警方通過查閱死者的電腦和手機楣富,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來伴榔,“玉大人,你說我怎么就攤上這事庄萎∽偕伲” “怎么了?”我有些...
    開封第一講書人閱讀 164,491評論 0 354
  • 文/不壞的土叔 我叫張陵糠涛,是天一觀的道長援奢。 經常有香客問我,道長忍捡,這世上最難降的妖魔是什么集漾? 我笑而不...
    開封第一講書人閱讀 58,636評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮砸脊,結果婚禮上具篇,老公的妹妹穿的比我還像新娘。我一直安慰自己凌埂,他們只是感情好驱显,可當我...
    茶點故事閱讀 67,676評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般埃疫。 火紅的嫁衣襯著肌膚如雪伏恐。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,541評論 1 305
  • 那天栓霜,我揣著相機與錄音翠桦,去河邊找鬼。 笑死胳蛮,一個胖子當著我的面吹牛销凑,可吹牛的內容都是我干的。 我是一名探鬼主播鹰霍,決...
    沈念sama閱讀 40,292評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼闻鉴,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了茂洒?” 一聲冷哼從身側響起孟岛,我...
    開封第一講書人閱讀 39,211評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎督勺,沒想到半個月后渠羞,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 45,655評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡智哀,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,846評論 3 336
  • 正文 我和宋清朗相戀三年次询,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片瓷叫。...
    茶點故事閱讀 39,965評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡屯吊,死狀恐怖,靈堂內的尸體忽然破棺而出摹菠,到底是詐尸還是另有隱情盒卸,我是刑警寧澤,帶...
    沈念sama閱讀 35,684評論 5 347
  • 正文 年R本政府宣布次氨,位于F島的核電站蔽介,受9級特大地震影響,放射性物質發(fā)生泄漏煮寡。R本人自食惡果不足惜虹蓄,卻給世界環(huán)境...
    茶點故事閱讀 41,295評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望幸撕。 院中可真熱鬧薇组,春花似錦、人聲如沸杈帐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至累铅,卻和暖如春跃须,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背娃兽。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評論 1 269
  • 我被黑心中介騙來泰國打工菇民, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人投储。 一個月前我還...
    沈念sama閱讀 48,126評論 3 370
  • 正文 我出身青樓第练,卻偏偏與公主長得像,于是被迫代替她去往敵國和親玛荞。 傳聞我的和親對象是個殘疾皇子娇掏,可洞房花燭夜當晚...
    茶點故事閱讀 44,914評論 2 355