java并發(fā)之synchronized

synchronized瞪慧,在java并發(fā)編程中它一直都是元老級(jí)的角色鸟廓。但是在大多數(shù)時(shí)候影暴,如果能使用Lock大家可能都不會(huì)使用它括改,因?yàn)樗莻€(gè)重量級(jí)鎖腻豌。但是隨著jdk6引入偏向鎖和輕量級(jí)鎖,對(duì)它進(jìn)行了各種優(yōu)化之后嘱能,在一些情況下它并不是那么重了饲梭。本文將結(jié)合HotSpot 1.7源碼,詳細(xì)分析jdk6做出的相關(guān)優(yōu)化焰檩。

synchronized實(shí)現(xiàn)分析

在開(kāi)始分析synchronized具體實(shí)現(xiàn)之前憔涉,先了解一下java同步的基礎(chǔ)。

Java同步基礎(chǔ)

在java中析苫,每一個(gè)對(duì)象其實(shí)都可以作為鎖:

  • 對(duì)于同步方法兜叨,鎖就是當(dāng)前的實(shí)例對(duì)象;

  • 對(duì)于靜態(tài)同步方法衩侥,鎖就是當(dāng)前對(duì)象的Class對(duì)象国旷;

  • 對(duì)于同步方法塊,鎖是synchronized括號(hào)里的對(duì)象茫死。

當(dāng)一個(gè)線程嘗試去訪問(wèn)同步代碼塊兒時(shí)跪但,首先需要干的事兒就是得到鎖,然后在程序執(zhí)行完畢或者拋出異常時(shí)釋放鎖峦萎,那么現(xiàn)在問(wèn)題就來(lái)了屡久,鎖存放在哪里呢?鎖需要存儲(chǔ)什么信息呢爱榔?

synchronized字節(jié)碼分析

synchronized使用

javap命令反編譯后的字節(jié)碼:


synchronized反編譯后的字節(jié)碼

從上圖可以看出被环,字節(jié)碼中包含指令monitorenter和moniterexit。synchronized關(guān)鍵字基于這兩個(gè)指令實(shí)現(xiàn)了代碼同步塊鎖的獲取和釋放详幽。

注:在JVM規(guī)范中筛欢,代碼塊同步是使用指令monitorenter和moniterexit實(shí)現(xiàn)浸锨,而方法同步是使用另外一種方式實(shí)現(xiàn),具體的實(shí)現(xiàn)細(xì)節(jié)JVM規(guī)范沒(méi)有做詳細(xì)說(shuō)明版姑。monitorenter指令是在編譯后插入到同步代碼塊的開(kāi)始位置柱搜,moniterexit指令是插入到同步代碼快結(jié)束和異常出,JVM要保證每個(gè)monitorenter指令都必須有moniterexit指令與之配對(duì)剥险。JVM中的任何對(duì)象都有一個(gè)monitor與之關(guān)聯(lián)冯凹,當(dāng)有一個(gè)monitor被持有后,它將處于鎖定狀態(tài)炒嘲,而線程執(zhí)行到monitorenter指令時(shí)宇姚,將會(huì)嘗試獲取對(duì)象對(duì)應(yīng)的monitor的所有權(quán),這個(gè)過(guò)程也就是所謂的嘗試獲取對(duì)象的鎖夫凸。

monitorenter實(shí)現(xiàn)
monitorenter實(shí)現(xiàn)

整個(gè)monitorenter主要干了這些事兒:

  1. 將入?yún)avaThread thread指向當(dāng)前線程浑劳;

  2. 初始化當(dāng)前線程的對(duì)象頭;

  3. 判斷當(dāng)前虛擬機(jī)是否開(kāi)啟偏向鎖功能夭拌,如果開(kāi)啟魔熏,調(diào)用fast_enter方法,否則鸽扁,調(diào)用slow_enter方法蒜绽。

Java對(duì)象頭

monitorenter中很重要的一步就是構(gòu)造Java對(duì)象頭h_obj,同時(shí)桶现,在后續(xù)的fast_enter或者slow_enter中躲雅,h_obj都作為一個(gè)入?yún)⑴c到具體的邏輯中,鎖其實(shí)就存儲(chǔ)在Java對(duì)象頭中骡和。

對(duì)象頭組成部分
如果對(duì)象類型是數(shù)組相赁,虛擬機(jī)用3個(gè)Word存儲(chǔ)對(duì)象頭,如果對(duì)象類型是非數(shù)組類型慰于,用2個(gè)Word存儲(chǔ)對(duì)象頭钮科,接下來(lái)看看這幾個(gè)Word都用來(lái)干什么。

  1. Mark Word:主要用來(lái)存儲(chǔ)對(duì)象的hashCode婆赠、鎖標(biāo)記位绵脯、分代年齡等等,占用內(nèi)存大小為1個(gè)Word休里;

  2. Class Metadata Address:主要用來(lái)存儲(chǔ)對(duì)象類型數(shù)據(jù)的指針蛆挫,占用內(nèi)存大小為1個(gè)Word;

  3. Array Length:存儲(chǔ)數(shù)組的長(zhǎng)度份帐,這部分只有在當(dāng)前對(duì)象類型為數(shù)組時(shí)才存在璃吧,同樣楣导,占用內(nèi)存大小也為1個(gè)Word废境。

接下來(lái)就詳細(xì)了解與synchronized息息相關(guān)的Mark Word的相關(guān)內(nèi)容。

HotSpot的Mark Word
HotSpot通過(guò)markOop.hpp實(shí)現(xiàn)了Mark Word。由于對(duì)象頭需要存儲(chǔ)的數(shù)據(jù)類型較多噩凹,充分考慮到內(nèi)存的復(fù)用巴元,markOop被設(shè)計(jì)成一個(gè)非固定的數(shù)據(jù)結(jié)構(gòu),可以根據(jù)標(biāo)志位的變化而轉(zhuǎn)變成不同類型的數(shù)據(jù)驮宴。

  • 32位虛擬機(jī)markOop實(shí)現(xiàn)


    32位虛擬機(jī)markOop實(shí)現(xiàn).png
    • hash:對(duì)象hashCode逮刨;
    • age:對(duì)象的分代年齡;
    • biased_lock:是否是偏向鎖堵泽;
    • lock:鎖標(biāo)志位修己;
    • JavaThread*:持有偏向鎖的線程ID;
    • epoch:偏向鎖時(shí)間戳

    在運(yùn)行期間迎罗,隨著鎖標(biāo)志位的變化睬愤,Mark Word可以變化成以下幾種類型的數(shù)據(jù):


    Mark Word數(shù)據(jù)類型
  • 64虛擬機(jī)markOop實(shí)現(xiàn)


    64位markOop實(shí)現(xiàn)

    在32位虛擬的markOop基礎(chǔ)上增加了unused,同樣的纹安,在運(yùn)行期間尤辱,隨著標(biāo)志位的變化Mark Work也會(huì)隨之改變,在這里我就不做詳細(xì)贅述了厢岂。

鎖的升級(jí)

jdk 6為了減少獲得鎖和釋放鎖帶來(lái)的性能消耗光督,引入了偏向鎖和輕量級(jí)鎖,換句話說(shuō)塔粒,在jdk 6及以后版本结借,鎖一共有四種狀態(tài):無(wú)鎖狀態(tài)、偏向鎖狀態(tài)卒茬、輕量級(jí)鎖狀態(tài)和重量級(jí)鎖狀態(tài)映跟,它會(huì)隨著競(jìng)爭(zhēng)情況逐漸升級(jí)。但是扬虚,鎖一旦升級(jí)之后就不能降級(jí)努隙,當(dāng)然,不能降級(jí)也是為了提高獲得鎖和釋放鎖的效率辜昵。

偏向鎖

引入偏向鎖是為了讓線程獲取鎖的代價(jià)更低荸镊。當(dāng)一個(gè)線程訪問(wèn)同步代碼塊并且獲取鎖時(shí),會(huì)在對(duì)象頭和棧幀中的鎖記錄中存儲(chǔ)偏向鎖偏向的線程ID堪置,以后該線程在進(jìn)入和退出同步塊時(shí)不需要花費(fèi)CAS操作來(lái)加鎖和解鎖躬存,只需要校驗(yàn)對(duì)象頭Mark Work中是否存儲(chǔ)指向當(dāng)前線程的偏向鎖即可,節(jié)省了一部分CAS操作的性能消耗舀锨。不過(guò)岭洲,當(dāng)多個(gè)線程競(jìng)爭(zhēng)偏向鎖時(shí),需要撤銷偏向鎖坎匿,如果撤銷偏向鎖的性能消耗大于之前節(jié)省下來(lái)的那部分CAS操作的性能消耗盾剩,就得不償失了雷激。在jdk 6和jdk 7中,偏向鎖默認(rèn)是啟用的告私,但是它在應(yīng)用程序啟動(dòng)幾秒鐘之后才激活屎暇,當(dāng)然,如果不想偏向鎖延遲激活驻粟,可以使用JVM參數(shù)-XX:BiasedLockingStartupDelay = 0來(lái)關(guān)閉延遲根悼。當(dāng)然,也可以用過(guò)JVM參數(shù)-XX:-UseBiasedLocking=false來(lái)關(guān)閉偏向鎖蜀撑,這時(shí)候挤巡,默認(rèn)的鎖狀態(tài)是輕量級(jí)鎖。

在HosSpot中酷麦,偏向鎖的入口為synchronizer.cpp的fast_enter方法:


fast_enter實(shí)現(xiàn)

偏向鎖的獲取

注:偏向鎖獲取代碼過(guò)長(zhǎng)玄柏,在這里就不貼代碼了,有興趣的可以去openjdk對(duì)照相應(yīng)的源碼看看贴铜。

偏向鎖的獲取的實(shí)現(xiàn)邏輯如下:

  1. 獲取對(duì)象頭Mark Word mark粪摘;

  2. 判斷對(duì)象頭mark是否為可偏向狀態(tài),也就是判斷mark的偏向鎖biased_lock是否為1绍坝,lock狀態(tài)是否為01徘意;

  3. 判斷對(duì)象頭mark中的JavaThread* thread:

  • null == thread || thread == Thread.current,跳轉(zhuǎn)到步驟4轩褐;

  • 否則椎咧,跳轉(zhuǎn)到步驟5;

  1. 調(diào)用CAS指令設(shè)置mark中的JavaThread為當(dāng)前線程:
  • 調(diào)用CAS成功把介,返回BIAS_REVOKED勤讽,鎖獲取成功,線程可以執(zhí)行同步代碼塊拗踢;

  • 調(diào)用CAS失敗脚牍,跳轉(zhuǎn)到步驟5;

  1. 當(dāng)調(diào)用CAS失敗時(shí)巢墅,表明當(dāng)前存在多個(gè)線程競(jìng)爭(zhēng)鎖诸狭,當(dāng)達(dá)到safepoint時(shí),掛起已獲得偏向鎖的線程君纫,撤銷偏向鎖驯遇,并且調(diào)用slow_enter方法將當(dāng)前鎖升級(jí)為輕量級(jí)鎖,獲取到輕量級(jí)鎖之后蓄髓,喚醒被阻塞在safepoint的線程叉庐,線程繼續(xù)執(zhí)行同步代碼塊。

偏向鎖的撤銷

偏向鎖的撤銷

具體執(zhí)行流程如下:

  1. 校驗(yàn)當(dāng)前是否到達(dá)safepoint会喝;

  2. 暫停已獲取到偏向鎖的線程陡叠;

  3. 撤銷偏向鎖玩郊,恢復(fù)鎖標(biāo)志位為01(無(wú)鎖狀態(tài))或者00(輕量級(jí)鎖狀態(tài))。

輕量級(jí)鎖

注:輕量級(jí)鎖的引入在一定程度上減少了鎖的性能消耗匾竿,但是如果多個(gè)線程競(jìng)爭(zhēng)時(shí)瓦宜,輕量級(jí)鎖還是會(huì)膨脹成重量級(jí)鎖蔚万,所以岭妖,輕量級(jí)鎖以及偏向鎖的出現(xiàn)并不是想要替代重量級(jí)鎖。

輕量級(jí)鎖的獲取
在HotSpot中反璃,輕量級(jí)鎖的入口為synchronizer.cpp的slow_enter方法:

slow_enter實(shí)現(xiàn)

具體執(zhí)行流程如下:

  1. 獲取對(duì)象頭mark昵慌;

  2. 調(diào)用方法is_neutral()判斷當(dāng)前對(duì)象是否為無(wú)鎖狀態(tài)(mark的biased_lock為0,lock為01):

  • 無(wú)鎖狀態(tài)淮蜈,跳轉(zhuǎn)到步驟5斋攀;

  • 否則,跳轉(zhuǎn)到步驟4梧田;

  1. 調(diào)用set_displaced_header方法將對(duì)象頭mark復(fù)制到鎖記錄中淳蔼;

  2. 調(diào)用CAS指令嘗試將對(duì)象頭mark替換為指向鎖記錄的指針,如果成功裁眯,當(dāng)前線程獲取到鎖鹉梨,可以執(zhí)行同步代碼塊,否則穿稳,跳轉(zhuǎn)到步驟5存皂;

  3. 如果對(duì)象頭mark處于加鎖狀態(tài),并且mark的鎖記錄指針指向當(dāng)前線程逢艘,當(dāng)前線程獲取到鎖旦袋,可以執(zhí)行同步代碼塊,否則它改,當(dāng)前存在多個(gè)線程競(jìng)爭(zhēng)疤孕,調(diào)用inflate方法膨脹成重量級(jí)鎖。

輕量級(jí)鎖的釋放
輕量級(jí)鎖的釋放是通過(guò)synchronizer.cpp的fast_exit完成的:

fast_exit實(shí)現(xiàn)

具體執(zhí)行流程如下:

  1. 校驗(yàn)當(dāng)前對(duì)象頭mark是否不處于偏向鎖狀態(tài):
  • 處于偏向鎖狀態(tài)央拖,校驗(yàn)不通過(guò)胰柑,程序不往下執(zhí)行;

  • 不處于偏向鎖狀態(tài)爬泥,校驗(yàn)通過(guò)柬讨,跳轉(zhuǎn)到步驟2;

  1. 獲取保存在BasicLock對(duì)象中的對(duì)象頭dhw袍啡;

  2. 嘗試使用CAS操作將dhw替換到當(dāng)前對(duì)象頭踩官,如果替換成功,表示沒(méi)有競(jìng)爭(zhēng)發(fā)生境输,輕量級(jí)鎖釋放成功蔗牡,否則颖系,當(dāng)前鎖存在競(jìng)爭(zhēng),調(diào)用inflate方法膨脹成重量級(jí)鎖辩越。

到這里為止嘁扼,就jdk 6對(duì)synchronized關(guān)鍵字做出的相關(guān)優(yōu)化分析就告一段落了,synchronized還有一部分有關(guān)重量級(jí)鎖的實(shí)現(xiàn)也會(huì)在后文做相應(yīng)的介紹分析黔攒。希望對(duì)大家就synchronized關(guān)鍵詞理解有所幫助趁啸。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市督惰,隨后出現(xiàn)的幾起案子不傅,更是在濱河造成了極大的恐慌,老刑警劉巖赏胚,帶你破解...
    沈念sama閱讀 217,509評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件访娶,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡觉阅,警方通過(guò)查閱死者的電腦和手機(jī)崖疤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)典勇,“玉大人劫哼,你說(shuō)我怎么就攤上這事〕杖幔” “怎么了沦偎?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,875評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)咳蔚。 經(jīng)常有香客問(wèn)我豪嚎,道長(zhǎng),這世上最難降的妖魔是什么谈火? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,441評(píng)論 1 293
  • 正文 為了忘掉前任侈询,我火速辦了婚禮,結(jié)果婚禮上糯耍,老公的妹妹穿的比我還像新娘扔字。我一直安慰自己,他們只是感情好温技,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,488評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布革为。 她就那樣靜靜地躺著,像睡著了一般舵鳞。 火紅的嫁衣襯著肌膚如雪震檩。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,365評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音抛虏,去河邊找鬼博其。 笑死,一個(gè)胖子當(dāng)著我的面吹牛迂猴,可吹牛的內(nèi)容都是我干的慕淡。 我是一名探鬼主播,決...
    沈念sama閱讀 40,190評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼沸毁,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼峰髓!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起以清,我...
    開(kāi)封第一講書(shū)人閱讀 39,062評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤儿普,失蹤者是張志新(化名)和其女友劉穎崎逃,沒(méi)想到半個(gè)月后掷倔,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,500評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡个绍,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,706評(píng)論 3 335
  • 正文 我和宋清朗相戀三年勒葱,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片巴柿。...
    茶點(diǎn)故事閱讀 39,834評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡凛虽,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出广恢,到底是詐尸還是另有隱情凯旋,我是刑警寧澤,帶...
    沈念sama閱讀 35,559評(píng)論 5 345
  • 正文 年R本政府宣布钉迷,位于F島的核電站至非,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏糠聪。R本人自食惡果不足惜荒椭,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,167評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望舰蟆。 院中可真熱鬧趣惠,春花似錦、人聲如沸身害。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,779評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)塌鸯。三九已至侍瑟,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間界赔,已是汗流浹背丢习。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,912評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工牵触, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人咐低。 一個(gè)月前我還...
    沈念sama閱讀 47,958評(píng)論 2 370
  • 正文 我出身青樓揽思,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親见擦。 傳聞我的和親對(duì)象是個(gè)殘疾皇子钉汗,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,779評(píng)論 2 354

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

  • Java8張圖 11、字符串不變性 12鲤屡、equals()方法损痰、hashCode()方法的區(qū)別 13、...
    Miley_MOJIE閱讀 3,704評(píng)論 0 11
  • Java并發(fā)編程 來(lái)自Java并發(fā)編程的藝術(shù)個(gè)人博客: http://blog.csdn.net/qq_22329...
    越長(zhǎng)越圓閱讀 3,241評(píng)論 4 54
  • 今天,和同事溫老師一起去嘉定參加四校教研活動(dòng)堰汉,結(jié)束后辽社,請(qǐng)她吃了頓晚飯,席間聊了很多翘鸭。 她真人恰如其姓滴铅,溫和、溫暖就乓,...
    蘭海123閱讀 195評(píng)論 4 2
  • 你讀過(guò)大冰的《阿尼陀佛么么噠》嗎?有個(gè)朋友對(duì)我說(shuō):大冰的書(shū)很好看的守伸,我超級(jí)喜歡绎秒。 另外一個(gè)朋友對(duì)我說(shuō):我覺(jué)得這本書(shū)...
    日月同光閱讀 568評(píng)論 0 0
  • 之前就希望有個(gè)這樣的軟件,可以看看別人寫(xiě)的東西尼摹,也記錄一些自己的隨筆见芹,無(wú)意找到啦,真好
    FuMlik閱讀 148評(píng)論 0 0