Java 線程鎖

????????首先要先了解Java中的鎖酵使,這就不得不提synchronized關(guān)鍵字和concurrent 包中的ReentrantLock

synchronized關(guān)鍵字

????????synchronized 可以用來修飾以下 3 個(gè)層面:

? ? ? ? ? ? ? ? 1焙糟、修飾實(shí)例方法:鎖對(duì)象是當(dāng)前實(shí)例對(duì)象口渔。不同實(shí)例對(duì)象是不互斥的。

? ? ? ? ? ? ? ? 2穿撮、修飾靜態(tài)類方法:鎖對(duì)象是當(dāng)前類的 Class 對(duì)象缺脉。因此即使在不同線程中調(diào)用不同實(shí)例對(duì)象痪欲,也會(huì)有互斥效果。

? ? ? ? ? ? ? ? 3攻礼、修飾代碼塊:synchronized 作用于代碼塊時(shí)业踢,鎖對(duì)象就是跟在后面括號(hào)中的對(duì)象。任何 Object 對(duì)象都可以當(dāng)作鎖對(duì)象秘蛔。

ReentrantLock

????????ReentrantLock 的使用同 synchronized 有點(diǎn)不同陨亡,它的加鎖和解鎖操作都需要手動(dòng)完成。

//lock為ReentrantLock的實(shí)例對(duì)象

lock.lock();

lock.unlock();

synchronized和ReentrantLock的區(qū)別

? ? ? ? 說到他們的區(qū)別深员,首先先要看看我們用了synchronized關(guān)鍵字的字節(jié)碼是什么樣子的负蠕。代碼如下:

圖1?synchronized修飾代碼塊

? ? ? ? 使用javap -v xxx.class查看字節(jié)碼,如下:

圖2?synchronized修飾代碼塊對(duì)應(yīng)字節(jié)碼

????????可以看出在字節(jié)碼中加入了1個(gè)monitorenter和2個(gè)monitorexit倦畅。這是因?yàn)樘摂M機(jī)需要保證當(dāng)異常發(fā)生時(shí)也能釋放鎖遮糖。因此 2 個(gè) monitorexit 一個(gè)是代碼正常執(zhí)行結(jié)束后釋放鎖,一個(gè)是在代碼執(zhí)行異常時(shí)釋放鎖叠赐。

????????因此在使用synchronized關(guān)鍵字時(shí)欲账,虛擬機(jī)會(huì)自動(dòng)在同步代碼塊的開始和結(jié)束(或異常)位置添加 monitorenter 和 monitorexit 指令。

? ? ? ? 而在ReentrantLock的使用中芭概,應(yīng)該將 unlock 操作放在 finally 代碼塊中赛不。這是因?yàn)?ReentrantLock 與 synchronized 不同,當(dāng)異常發(fā)生時(shí) synchronized 會(huì)自動(dòng)釋放鎖罢洲,但是 ReentrantLock 并不會(huì)自動(dòng)釋放鎖踢故。因此好的方式是將 unlock 操作放在 finally 代碼塊中,保證任何時(shí)候鎖都能夠被正常釋放掉惹苗。?

????????ReentrantLock還可以實(shí)現(xiàn)公平鎖(ReentrantLock初始化時(shí)傳入Boolean值)殿较,讀寫鎖(ReentrantReadWriteLock)

公平鎖:通過同步隊(duì)列來實(shí)現(xiàn)多個(gè)線程按照申請(qǐng)鎖的順序獲取鎖。

讀寫鎖:寫操作開始到結(jié)束之間桩蓉,不能再有其他讀操作進(jìn)來淋纲,并且寫操作完成之后的更新數(shù)據(jù)需要對(duì)后續(xù)的讀操作可見。ReentrantReadWriteLock在讀操作時(shí)獲取讀鎖院究,寫操作時(shí)獲取寫鎖洽瞬。當(dāng)寫鎖被獲取到時(shí),后續(xù)的讀寫鎖都會(huì)被阻塞业汰,寫鎖釋放之后伙窃,所有操作繼續(xù)執(zhí)行。

synchronized實(shí)現(xiàn)原理——對(duì)象頭和 Monitor

1蔬胯、對(duì)象頭

????????Java 對(duì)象在內(nèi)存中的布局分為 3 部分:對(duì)象頭、實(shí)例數(shù)據(jù)位他、對(duì)齊填充氛濒。當(dāng)我們?cè)?Java 代碼中产场,使用 new 創(chuàng)建一個(gè)對(duì)象的時(shí)候,JVM 會(huì)在堆中創(chuàng)建一個(gè) instanceOopDesc 對(duì)象舞竿,這個(gè)對(duì)象中包含了對(duì)象頭以及實(shí)例數(shù)據(jù)京景。

????????其中 _mark 和 _metadata 一起組成了對(duì)象頭。_mark 是 markOop 類型數(shù)據(jù)骗奖,一般稱它為標(biāo)記字段(Mark Word)确徙,其中主要存儲(chǔ)了對(duì)象的 hashCode、分代年齡执桌、鎖標(biāo)志位鄙皇,是否偏向鎖等。用一張圖來表示 32 位 Java 虛擬機(jī)的 Mark Word 的存儲(chǔ)結(jié)構(gòu)如下:

圖3?Java 虛擬機(jī)的 Mark Word 的存儲(chǔ)結(jié)構(gòu)

從圖中可以看出仰挣,根據(jù)"鎖標(biāo)志位”以及"是否為偏向鎖"伴逸,Java 中的鎖可以分為以下幾種狀態(tài)

圖4

????????在 Java 6 之前,并沒有輕量級(jí)鎖和偏向鎖膘壶,只有重量級(jí)鎖错蝴,也就是通常所說 synchronized 的對(duì)象鎖,鎖標(biāo)志位為 10颓芭。從圖3中的描述可以看出:當(dāng)鎖是重量級(jí)鎖時(shí)顷锰,對(duì)象頭中 Mark Word 會(huì)用 30 bit 來指向一個(gè)“互斥量”,而這個(gè)互斥量就是 Monitor

Monitor

????????Monitor 可以把它理解為一個(gè)同步工具,也可以描述為一種同步機(jī)制荠雕。實(shí)際上适揉,它是一個(gè)保存在對(duì)象頭中的一個(gè)對(duì)象。markOop代碼中唯竹, monitor() 方法創(chuàng)建一個(gè) ObjectMonitor 對(duì)象,而 ObjectMonitor 就是 Java 虛擬機(jī)中的 Monitor 的具體實(shí)現(xiàn)。因此 Java 中每個(gè)對(duì)象都會(huì)有一個(gè)對(duì)應(yīng)的 ObjectMonitor 對(duì)象良狈,這也是 Java 中所有的 Object 都可以作為鎖對(duì)象的原因。

????????其中比較重要的屬性如下圖:

圖5

Java 虛擬機(jī)對(duì) synchronized 的優(yōu)化

????????從 Java 6 開始笨枯,虛擬機(jī)對(duì) synchronized 關(guān)鍵字做了多方面的優(yōu)化薪丁,主要目的就是,避免 ObjectMonitor 的訪問馅精,減少“重量級(jí)鎖”的使用次數(shù)严嗜,并最終減少線程上下文切換的頻率 。其中主要做了以下幾個(gè)優(yōu)化: 鎖自旋洲敢、輕量級(jí)鎖漫玄、偏向鎖

鎖自旋

????????線程的阻塞和喚醒需要 CPU 從用戶態(tài)轉(zhuǎn)為核心態(tài),頻繁的阻塞和喚醒對(duì) CPU 來說是一件負(fù)擔(dān)很重的工作睦优,勢(shì)必會(huì)給系統(tǒng)的并發(fā)性能帶來很大的壓力渗常,所以 Java 引入了自旋鎖的操作。實(shí)際上自旋鎖在 Java 1.4 就被引入了汗盘,默認(rèn)關(guān)閉皱碘,但是可以使用參數(shù) -XX:+UseSpinning 將其開啟。但是從 Java 6 之后默認(rèn)開啟隐孽。

????????所謂自旋癌椿,就是讓該線程等待一段時(shí)間,不會(huì)被立即掛起菱阵,看當(dāng)前持有鎖的線程是否會(huì)很快釋放鎖踢俄。而所謂的等待就是執(zhí)行一段無意義的循環(huán)即可(自旋)。

????????自旋鎖也存在一定的缺陷:自旋鎖要占用 CPU送粱,如果鎖競(jìng)爭(zhēng)的時(shí)間比較長(zhǎng)褪贵,那么自旋通常不能獲得鎖,白白浪費(fèi)了自旋占用的 CPU 時(shí)間抗俄。這通常發(fā)生在鎖持有時(shí)間長(zhǎng)脆丁,且競(jìng)爭(zhēng)激烈的場(chǎng)景中,此時(shí)應(yīng)主動(dòng)禁用自旋鎖动雹。

輕量級(jí)鎖

????????有時(shí)候 Java 虛擬機(jī)中會(huì)存在這種情形:對(duì)于一塊同步代碼槽卫,雖然有多個(gè)不同線程會(huì)去執(zhí)行,但是這些線程是在不同的時(shí)間段交替請(qǐng)求這把鎖對(duì)象胰蝠,也就是不存在鎖競(jìng)爭(zhēng)的情況歼培。在這種情況下,鎖會(huì)保持在輕量級(jí)鎖的狀態(tài)茸塞,從而避免重量級(jí)鎖的阻塞和喚醒操作躲庄。

????????當(dāng)線程執(zhí)行某同步代碼時(shí),Java 虛擬機(jī)會(huì)在當(dāng)前線程的棧幀中開辟一塊空間(Lock Record)作為該鎖的記錄钾虐,然后 Java 虛擬機(jī)會(huì)嘗試使用 CAS(Compare And Swap)操作噪窘,將鎖對(duì)象的 Mark Word 拷貝到這塊空間中,并且將鎖記錄中的 owner 指向 Mark Word效扫。

????????當(dāng)線程再次執(zhí)行此同步代碼塊時(shí)倔监,判斷當(dāng)前對(duì)象的 Mark Word 是否指向當(dāng)前線程的棧幀,如果是則表示當(dāng)前線程已經(jīng)持有當(dāng)前對(duì)象的鎖菌仁,則直接執(zhí)行同步代碼塊浩习;否則只能說明該鎖對(duì)象已經(jīng)被其他線程搶占了,這時(shí)輕量級(jí)鎖需要膨脹為重量級(jí)鎖济丘。

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

偏向鎖

????????輕量級(jí)鎖是在沒有鎖競(jìng)爭(zhēng)情況下的鎖狀態(tài)疟赊,但是在有些時(shí)候鎖不僅存在多線程的競(jìng)爭(zhēng)辱士,而且總是由同一個(gè)線程獲得。因此為了讓線程獲得鎖的代價(jià)更低引入了偏向鎖的概念听绳。偏向鎖的意思是如果一個(gè)線程獲得了一個(gè)偏向鎖,如果在接下來的一段時(shí)間中沒有其他線程來競(jìng)爭(zhēng)鎖异赫,那么持有偏向鎖的線程再次進(jìn)入或者退出同一個(gè)同步代碼塊椅挣,不需要再次進(jìn)行搶占鎖和釋放鎖的操作。偏向鎖可以通過 -XX:+UseBiasedLocking 開啟或者關(guān)閉塔拳。

????????偏向鎖的具體實(shí)現(xiàn)就是在鎖對(duì)象的對(duì)象頭中有個(gè) ThreadId 字段鼠证,默認(rèn)情況下這個(gè)字段是空的,當(dāng)?shù)谝淮潍@取鎖的時(shí)候靠抑,就將自身的 ThreadId 寫入鎖對(duì)象的 Mark Word 中的 ThreadId 字段內(nèi)量九,將是否偏向鎖的狀態(tài)置為 01。這樣下次獲取鎖的時(shí)候颂碧,直接檢查 ThreadId 是否和自身線程 Id 一致荠列,如果一致,則認(rèn)為當(dāng)前線程已經(jīng)獲取了鎖载城,因此不需再次獲取鎖肌似,略過了輕量級(jí)鎖和重量級(jí)鎖的加鎖階段。提高了效率诉瓦。

????????其實(shí)偏向鎖并不適合所有應(yīng)用場(chǎng)景, 因?yàn)橐坏┏霈F(xiàn)鎖競(jìng)爭(zhēng)川队,偏向鎖會(huì)被撤銷,并膨脹成輕量級(jí)鎖睬澡,而撤銷操作(revoke)是比較重的行為固额,只有當(dāng)存在較多不會(huì)真正競(jìng)爭(zhēng)的 synchronized 塊時(shí),才能體現(xiàn)出明顯改善煞聪;因此實(shí)踐中斗躏,還是需要考慮具體業(yè)務(wù)場(chǎng)景,并測(cè)試后米绕,再?zèng)Q定是否開啟/關(guān)閉偏向鎖瑟捣。

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

總結(jié)

????????首先說了Java 中兩個(gè)實(shí)現(xiàn)同步的方式 synchronized 和 ReentrantLock栅干。其中 synchronized使用更簡(jiǎn)單迈套,加鎖和釋放鎖都是由虛擬機(jī)自動(dòng)完成,而 ReentrantLock 需要開發(fā)者手動(dòng)去完成碱鳞。但是很顯然 ReentrantLock 的使用場(chǎng)景更多桑李,公平鎖還有讀寫鎖都可以在復(fù)雜場(chǎng)景中發(fā)揮重要作用。

????????另外,還介紹了Java 中鎖的幾種狀態(tài)贵白,其中偏向鎖和輕量級(jí)鎖都是通過自旋等技術(shù)避免真正的加鎖率拒,而重量級(jí)鎖才是獲取鎖和釋放鎖,重量級(jí)鎖通過對(duì)象內(nèi)部的監(jiān)視器(ObjectMonitor)實(shí)現(xiàn)禁荒,其本質(zhì)是依賴于底層操作系統(tǒng)的 Mutex Lock(互斥鎖) 實(shí)現(xiàn)猬膨,操作系統(tǒng)實(shí)現(xiàn)線程之間的切換需要從用戶態(tài)到內(nèi)核態(tài)的切換,成本非常高呛伴。實(shí)際上Java對(duì)鎖的優(yōu)化還有”鎖消除“勃痴,但是”鎖消除“是基于Java對(duì)象逃逸分析的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末热康,一起剝皮案震驚了整個(gè)濱河市沛申,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌姐军,老刑警劉巖铁材,帶你破解...
    沈念sama閱讀 218,525評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異奕锌,居然都是意外死亡著觉,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門惊暴,熙熙樓的掌柜王于貴愁眉苦臉地迎上來固惯,“玉大人,你說我怎么就攤上這事缴守≡岷粒” “怎么了?”我有些...
    開封第一講書人閱讀 164,862評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵屡穗,是天一觀的道長(zhǎng)贴捡。 經(jīng)常有香客問我,道長(zhǎng)村砂,這世上最難降的妖魔是什么烂斋? 我笑而不...
    開封第一講書人閱讀 58,728評(píng)論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮础废,結(jié)果婚禮上汛骂,老公的妹妹穿的比我還像新娘。我一直安慰自己评腺,他們只是感情好帘瞭,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,743評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著蒿讥,像睡著了一般蝶念。 火紅的嫁衣襯著肌膚如雪抛腕。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,590評(píng)論 1 305
  • 那天媒殉,我揣著相機(jī)與錄音担敌,去河邊找鬼。 笑死廷蓉,一個(gè)胖子當(dāng)著我的面吹牛全封,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播桃犬,決...
    沈念sama閱讀 40,330評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼售貌,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了疫萤?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,244評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤敢伸,失蹤者是張志新(化名)和其女友劉穎扯饶,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體池颈,經(jīng)...
    沈念sama閱讀 45,693評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡尾序,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,885評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了躯砰。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片每币。...
    茶點(diǎn)故事閱讀 40,001評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖琢歇,靈堂內(nèi)的尸體忽然破棺而出兰怠,到底是詐尸還是另有隱情,我是刑警寧澤李茫,帶...
    沈念sama閱讀 35,723評(píng)論 5 346
  • 正文 年R本政府宣布揭保,位于F島的核電站,受9級(jí)特大地震影響魄宏,放射性物質(zhì)發(fā)生泄漏秸侣。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,343評(píng)論 3 330
  • 文/蒙蒙 一宠互、第九天 我趴在偏房一處隱蔽的房頂上張望味榛。 院中可真熱鬧,春花似錦予跌、人聲如沸搏色。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)继榆。三九已至巾表,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間略吨,已是汗流浹背集币。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留翠忠,地道東北人鞠苟。 一個(gè)月前我還...
    沈念sama閱讀 48,191評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像秽之,于是被迫代替她去往敵國(guó)和親当娱。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,955評(píng)論 2 355