Java線程鎖

內(nèi)核態(tài):
1.系統(tǒng)中既有操作系統(tǒng)的程序,也有普通用戶程序。為了安全性和穩(wěn)定性,操作系統(tǒng)的程序不能隨便訪
問扒俯,這就是內(nèi)核態(tài)族购。即需要執(zhí)行操作系統(tǒng)的程序就必須轉(zhuǎn)換到內(nèi)核態(tài)才能執(zhí)行
2. 內(nèi)核態(tài)可以使用計算機所有的硬件資源
用戶態(tài):不能直接使用系統(tǒng)資源,也不能改變CPU的工作狀態(tài)陵珍,并且只能訪問這個用戶程序自己的存儲空間!Nナ;ゴ俊!

Java 的線程是映射到操作系統(tǒng)原生線程之上的磕蒲,如果要阻塞或喚醒一個線程就需要操作系統(tǒng)的幫忙留潦,這就要從用戶態(tài)轉(zhuǎn)換到核心態(tài),就相當于工作從上海分部轉(zhuǎn)換到瑞典總部的操作一樣辣往,因此狀態(tài)轉(zhuǎn)換需要花費很多的處理器時間兔院。

比如如下代碼:
value++ 因為被關(guān)鍵字 synchronized 修飾,所以會在各個線程間同步執(zhí)行站削。但是 value++ 消耗的時間很有可能比線程狀態(tài)轉(zhuǎn)換消耗的時間還短坊萝,所以說 synchronized 是 Java 語言中一個重量級的操作。

image.png

synchronized 實現(xiàn)原理
要了解 synchronized 的原理需要先理清楚兩件事情:對象頭和 Monitor许起。

對象頭
Java 對象在內(nèi)存中的布局分為 3 部分:對象頭十偶、實例數(shù)據(jù)、對齊填充园细。當我們在 Java 代碼中惦积,使用 new 創(chuàng)建一個對象的時候,JVM 會在堆中創(chuàng)建一個 instanceOopDesc 對象猛频,這個對象中包含了對象頭以及實例數(shù)據(jù)狮崩。

instanceOopDesc 的基類為 oopDesc 類。它的結(jié)構(gòu)如下:

image.png

其中 _mark 和 _metadata 一起組成了對象頭睦柴。_metadata 主要保存了類元數(shù)據(jù)爱只,不需要做過多介紹恬试。這里重點看下 _mark 屬性训柴,_mark 是 markOop 類型數(shù)據(jù)幻馁,一般稱它為標記字段(Mark Word)仗嗦,其中主要存儲了對象的 hashCode稀拐、分代年齡德撬、鎖標志位,是否偏向鎖等纤勒。

用一張圖來表示 32 位 Java 虛擬機的 Mark Word 的默認存儲結(jié)構(gòu)如下:

image.png

默認情況下,沒有線程進行加鎖操作闸翅,所以鎖對象中的 Mark Word 處于無鎖狀態(tài)坚冀。但是考慮到 JVM 的空間效率记某,Mark Word 被設(shè)計成為一個非固定的數(shù)據(jù)結(jié)構(gòu)液南,以便存儲更多的有效數(shù)據(jù)滑凉,它會根據(jù)對象本身的狀態(tài)復(fù)用自己的存儲空間畅姊,如 32 位 JVM 下,除了上述列出的 Mark Word 默認存儲結(jié)構(gòu)外粗合,還有如下可能變化的結(jié)構(gòu):

image.png

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

image.png

在 Java 6 之前,并沒有輕量級鎖和偏向鎖撵割,只有重量級鎖啡彬,也就是通常所說 synchronized 的對象鎖庶灿,鎖標志位為 10往踢。從圖中的描述可以看出:當鎖是重量級鎖時峻呕,對象頭中 Mark Word 會用 30 bit 來指向一個“互斥量”瘦癌,而這個互斥量就是 Monitor讯私。

Monitor
Monitor 可以把它理解為一個同步工具斤寇,也可以描述為一種同步機制抡驼。實際上碎税,它是一個保存在對象頭中的一個對象雷蹂。在 markOop 中有如下代碼:

image.png

通過 monitor() 方法創(chuàng)建一個 ObjectMonitor 對象,而 ObjectMonitor 就是 Java 虛擬機中的 Monitor 的具體實現(xiàn)萎庭。因此 Java 中每個對象都會有一個對應(yīng)的 ObjectMonitor 對象驳规,這也是 Java 中所有的 Object 都可以作為鎖對象的原因吗购。

那 ObjectMonitor 是如何實現(xiàn)同步機制的呢捻勉?

首先看下 ObjectMonitor 的結(jié)構(gòu):

image.png

其中有幾個比較關(guān)鍵的屬性:

image.png

當多個線程同時訪問一段同步代碼時研底,首先會進入 _EntryList 隊列中胚想,當某個線程通過競爭獲取到對象的 monitor 后浊服,monitor 會把 _owner 變量設(shè)置為當前線程牙躺,同時 monitor 中的計數(shù)器 _count 加 1,即獲得對象鎖脓恕。

若持有 monitor 的線程調(diào)用 wait() 方法秋茫,將釋放當前持有的 monitor肛著,_owner 變量恢復(fù)為 null, _count 自減 1局荚,同時該線程進入 _WaitSet 集合中等待被喚醒聪建。若當前線程執(zhí)行完畢也將釋放 monitor(鎖)并復(fù)位變量的值金麸,以便其他線程進入獲取 monitor(鎖)揍魂。

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

鎖對象是 lock 對象,在 JVM 中會有一個 ObjectMonitor 對象與之對應(yīng)庄蹋。如下圖所示:

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

假設(shè)線程 2 首先通過競爭獲取到了鎖對象踱承,則 ObjectMonitor 中的 Owner 指向線程 2琢唾,并將 count 加 1懒熙。結(jié)果如下:

上圖中 Owner 指向線程 2 表示它已經(jīng)成功獲取到鎖(Monitor)對象,其他線程只能處于阻塞(blocking)狀態(tài)肢娘。如果線程 2 在執(zhí)行過程中調(diào)用 wait() 操作沙廉,則線程 2 會釋放鎖(Monitor)對象,以便其他線程進入獲取鎖(Monitor)對象,Owner 變量恢復(fù)為 null,count 做減 1 操作促王,同時線程 2 會添加到 WaitSet 集合犀盟,進入等待(waiting)狀態(tài)并等待被喚醒。結(jié)果如下:

然后線程 1 和線程 3 再次通過競爭獲取到鎖(Monitor)對象蝇狼,則重新將 Owner 指向成功獲取到鎖的線程阅畴。假設(shè)線程 1 獲取到鎖,如下:

如果在線程 1 執(zhí)行過程中調(diào)用 notify 操作將線程 2 喚醒迅耘,則當前處于 WaitSet 中的線程 2 會被重新添加到 EntrySet 集合中贱枣,并嘗試重新獲取競爭鎖(Monitor)對象。但是 notify 操作并不會是使程 1 釋放鎖(Monitor)對象颤专。結(jié)果如下:

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

實際上春塌,ObjectMonitor 的同步機制是 JVM 對操作系統(tǒng)級別的 Mutex Lock(互斥鎖)的管理過程,其間都會轉(zhuǎn)入操作系統(tǒng)內(nèi)核態(tài)簇捍。也就是說 synchronized 實現(xiàn)鎖只壳,在“重量級鎖”狀態(tài)下,當多個線程之間切換上下文時暑塑,還是一個比較重量級的操作吼句。

Java 虛擬機對 synchronized 的優(yōu)化
從 Java 6 開始,虛擬機對 synchronized 關(guān)鍵字做了多方面的優(yōu)化梯投,主要目的就是命辖,避免 ObjectMonitor 的訪問况毅,減少“重量級鎖”的使用次數(shù)分蓖,并最終減少線程上下文切換的頻率 。其中主要做了以下幾個優(yōu)化: 鎖自旋尔许、輕量級鎖么鹤、偏向鎖。

鎖自旋
線程的阻塞和喚醒需要 CPU 從用戶態(tài)轉(zhuǎn)為核心態(tài)味廊,頻繁的阻塞和喚醒對 CPU 來說是一件負擔很重的工作蒸甜,勢必會給系統(tǒng)的并發(fā)性能帶來很大的壓力,所以 Java 引入了自旋鎖的操作余佛。實際上自旋鎖在 Java 1.4 就被引入了柠新,默認關(guān)閉,但是可以使用參數(shù) -XX:+UseSpinning 將其開啟辉巡。但是從 Java 6 之后默認開啟恨憎。

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

自旋鎖也存在一定的缺陷:自旋鎖要占用 CPU钥组,如果鎖競爭的時間比較長输硝,那么自旋通常不能獲得鎖,白白浪費了自旋占用的 CPU 時間程梦。這通常發(fā)生在鎖持有時間長点把,且競爭激烈的場景中,此時應(yīng)主動禁用自旋鎖作烟。

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

要了解輕量級鎖的工作流程,還是需要再次看下對象頭中的 Mark Word探赫。上文中已經(jīng)提到型宙,鎖的標志位包含幾種情況:00 代表輕量級鎖、01 代表無鎖(或者偏向鎖)伦吠、10 代表重量級鎖妆兑、11 則跟垃圾回收算法的標記有關(guān)。

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

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

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

輕量級鎖所適應(yīng)的場景是線程交替執(zhí)行同步塊的場合,如果存在同一時間訪問同一鎖的場合怖现,就會導(dǎo)致輕量級鎖膨脹為重量級鎖茁帽。

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

偏向鎖的具體實現(xiàn)就是在鎖對象的對象頭中有個 ThreadId 字段癣蟋,默認情況下這個字段是空的,當?shù)谝淮潍@取鎖的時候狰闪,就將自身的 ThreadId 寫入鎖對象的 Mark Word 中的 ThreadId 字段內(nèi)疯搅,將是否偏向鎖的狀態(tài)置為 01。這樣下次獲取鎖的時候埋泵,直接檢查 ThreadId 是否和自身線程 Id 一致幔欧,如果一致,則認為當前線程已經(jīng)獲取了鎖丽声,因此不需再次獲取鎖礁蔗,略過了輕量級鎖和重量級鎖的加鎖階段。提高了效率雁社。

其實偏向鎖并不適合所有應(yīng)用場景, 因為一旦出現(xiàn)鎖競爭浴井,偏向鎖會被撤銷,并膨脹成輕量級鎖霉撵,而撤銷操作(revoke)是比較重的行為磺浙,只有當存在較多不會真正競爭的 synchronized 塊時,才能體現(xiàn)出明顯改善徒坡;因此實踐中撕氧,還是需要考慮具體業(yè)務(wù)場景,并測試后崭参,再決定是否開啟/關(guān)閉偏向鎖呵曹。

對于鎖的幾種狀態(tài)轉(zhuǎn)換的源碼分析款咖,可以參考:源碼分析Java虛擬機中鎖膨脹的過程

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末何暮,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子铐殃,更是在濱河造成了極大的恐慌海洼,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,744評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件富腊,死亡現(xiàn)場離奇詭異坏逢,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,505評論 3 392
  • 文/潘曉璐 我一進店門是整,熙熙樓的掌柜王于貴愁眉苦臉地迎上來肖揣,“玉大人,你說我怎么就攤上這事浮入×牛” “怎么了?”我有些...
    開封第一講書人閱讀 163,105評論 0 353
  • 文/不壞的土叔 我叫張陵事秀,是天一觀的道長彤断。 經(jīng)常有香客問我,道長易迹,這世上最難降的妖魔是什么宰衙? 我笑而不...
    開封第一講書人閱讀 58,242評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮睹欲,結(jié)果婚禮上供炼,老公的妹妹穿的比我還像新娘。我一直安慰自己窘疮,他們只是感情好劲蜻,可當我...
    茶點故事閱讀 67,269評論 6 389
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著考余,像睡著了一般先嬉。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上楚堤,一...
    開封第一講書人閱讀 51,215評論 1 299
  • 那天疫蔓,我揣著相機與錄音,去河邊找鬼身冬。 笑死衅胀,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的酥筝。 我是一名探鬼主播滚躯,決...
    沈念sama閱讀 40,096評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼嘿歌!你這毒婦竟也來了掸掏?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,939評論 0 274
  • 序言:老撾萬榮一對情侶失蹤宙帝,失蹤者是張志新(化名)和其女友劉穎丧凤,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體步脓,經(jīng)...
    沈念sama閱讀 45,354評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡愿待,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,573評論 2 333
  • 正文 我和宋清朗相戀三年浩螺,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片仍侥。...
    茶點故事閱讀 39,745評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡要出,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出农渊,到底是詐尸還是另有隱情厨幻,我是刑警寧澤,帶...
    沈念sama閱讀 35,448評論 5 344
  • 正文 年R本政府宣布腿时,位于F島的核電站况脆,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏批糟。R本人自食惡果不足惜格了,卻給世界環(huán)境...
    茶點故事閱讀 41,048評論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望徽鼎。 院中可真熱鬧盛末,春花似錦、人聲如沸否淤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,683評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽石抡。三九已至檐嚣,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間啰扛,已是汗流浹背嚎京。 一陣腳步聲響...
    開封第一講書人閱讀 32,838評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留隐解,地道東北人鞍帝。 一個月前我還...
    沈念sama閱讀 47,776評論 2 369
  • 正文 我出身青樓,卻偏偏與公主長得像煞茫,于是被迫代替她去往敵國和親帕涌。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,652評論 2 354

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

  • 1续徽、重量級鎖 內(nèi)置鎖是JVM提供的最便捷的線程同步工具蚓曼,利用synchronized關(guān)鍵字來修飾同步代碼塊,我們稱...
    冰河winner閱讀 502評論 0 0
  • 中午12:38分炸宵,我在路上接到兒的電話辟躏,兒說下午上薩克斯課谷扣,又說他現(xiàn)在想回家土全。我沒有如以前一聽到他想回家就...
    大愛無疆楊青閱讀 335評論 0 10
  • 作文資源庫捎琐,課本,最美詩詞背后的故事裹匙,學(xué)校要求讀的名著瑞凑,目前讀過的有海底兩萬里,駱駝祥子概页,昆蟲記籽御,正在讀紅星照耀中...
    林林_c769閱讀 279評論 3 3
  • 我想畫兩只蝴蝶项鬼, 一只涂成彩色哑梳, 飛舞在白天, 一只涂成粉色绘盟, 靜落在床頭邊鸠真。 我想畫兩個太陽, 一個涂成紅色龄毡, ...
    彩色夢想閱讀 332評論 5 6