sychronized-基本原理介紹以及鎖升級過程詳解

sychronized的實(shí)現(xiàn)原理與應(yīng)用

在多線程并發(fā)編程中synchronized一直是元老的角色,很多人都會稱呼它為重量級鎖串绩。但是,隨著Java SE 1.6對synchronized進(jìn)行了各種優(yōu)化后狐史,有很多特殊情況下它就并不是那個重了评凝。本文詳細(xì)介紹Java SE 1.6 中為了減少獲得鎖和釋放鎖帶來的性能消耗而引入的偏向鎖和輕量級鎖,以及鎖的升級過程外邓。

sychronized鎖的基礎(chǔ)

Java中的每一個對象都可以作為鎖撤蚊。具體可以有以下三種形式

  • 1.對于普通的同步方法古掏,鎖的是當(dāng)前實(shí)例對象损话。
  • 2.對于靜態(tài)同步方法,鎖是當(dāng)前的Class對象槽唾。
  • 3.對于同步方法塊丧枪,鎖是Synchonized括號里配置的對象。

當(dāng)一個線程識圖訪問同步代碼塊時庞萍,它首先必須得到鎖拧烦,退出或者拋出異常的時候必須釋放鎖。

從JVM規(guī)范中可以看到synchronized在JVM里的實(shí)現(xiàn)原理钝计。JVM基于進(jìn)入和退出Monitor對象來實(shí)現(xiàn)方法同步和代碼塊同步恋博,但兩者的實(shí)現(xiàn)細(xì)節(jié)不一樣齐佳。代碼塊的同步是使用monitorenter和monitorexit指令實(shí)現(xiàn)的,而方法的同步使用修飾符上的ACC_SYNCHNIZED完成的债沮。方法的同步同樣可以使用這兩個指令完成炼吴。不管使用哪一種,本質(zhì)是對一個對象的監(jiān)視器進(jìn)行獲取疫衩,同一個時刻只能有一個線程獲取到由synchronized保護(hù)對象的監(jiān)視器硅蹦。

monitorenter指令是在編譯后插入到同步代碼塊的開始位置,而monitorexit是插入到方法的結(jié)束處和異常處闷煤。任何對象都有一個monitor與之關(guān)聯(lián)童芹,當(dāng)且一個monitor被持有后,它將處于鎖定狀態(tài)鲤拿。線程執(zhí)行到monitorenter指令時假褪,將會嘗試獲取對象所對應(yīng)的monitor所有權(quán),即嘗試獲取對象的鎖皆愉。

Java對象頭

synchronized用的鎖是存在java對象頭里的嗜价。如果對象是數(shù)組類型,則虛擬機(jī)用3個字節(jié)寬(word)存儲對象頭幕庐,如果是非數(shù)組類型久锥,則用2個字節(jié)寬存儲。32位虛擬機(jī)1字寬=4字節(jié)=32bit
對象頭內(nèi)容包括MarkWord(32/64bit 存儲對象的hashCode或鎖信息)异剥、Class Metadata Address(32/64bit 存儲對象類型數(shù)據(jù)的指針)瑟由,Array Length(32/32bit 數(shù)組長度)

java對象頭里的Mark Word里默認(rèn)存儲對象的HashCode、分代年齡和鎖標(biāo)記位冤寿。32位JVM的Mark Word可能變化存儲為以下5種數(shù)據(jù):


Mark Word 狀態(tài)變化

在64位虛擬機(jī)下歹苦,Mark Word是64bit大小,其存儲結(jié)構(gòu)如下


Mark Word存儲結(jié)構(gòu)-64

鎖升級對比

Java SE 1.6 為了減少獲得鎖和釋放鎖帶來的性能消耗督怜,引入了“偏向鎖”和“輕量級鎖”殴瘦。鎖一共有四種狀態(tài):無鎖狀態(tài)偏向鎖号杠、輕量級鎖蚪腋、重量級鎖,這個狀態(tài)隨著競爭情況主鍵升級姨蟋。鎖可以升級但不可以降級屉凯。不可逆這是為了提供獲得鎖和釋放鎖的效率。

先介紹一下自旋鎖

自旋鎖
  • 引入背景
    在多線程競爭鎖時眼溶,當(dāng)一個線程獲取鎖時悠砚,它會阻塞所有正在競爭的線程,這樣對性能帶來了極大的影響堂飞。在掛起線程和恢復(fù)線程的操作都需要轉(zhuǎn)入內(nèi)核態(tài)中完成灌旧,這些操作個i系統(tǒng)的并發(fā)性能帶來了很大的壓力绑咱。同時HotSpot團(tuán)隊(duì)注意到在很多情況下,共享數(shù)據(jù)的鎖定狀態(tài)只會持續(xù)很短的一段時間枢泰,為了這段時間去掛起和回復(fù)阻塞線程并不值得羡玛。在如今多處理器環(huán)境下,完全可以讓另一個沒有獲取到鎖的線程在門外等待一會(自旋)宗苍,但不放棄CPU的執(zhí)行時間稼稿。等待持有鎖的線程是否很快就會釋放鎖。為了讓線程等待讳窟,我們只需要讓線程執(zhí)行一個忙循環(huán)(自旋)让歼,這便是自旋鎖由來的原因。

  • 優(yōu)化
    自旋鎖早在JDK1.4 中就引入了丽啡,只是當(dāng)時默認(rèn)時關(guān)閉的谋右。在JDK 1.6后默認(rèn)為開啟狀態(tài)。自旋鎖本質(zhì)上與阻塞并不相同补箍,先不考慮其對多處理器的要求改执,如果鎖占用的時間非常的短,那么自旋鎖的新能會非常的好坑雅,相反辈挂,其會帶來更多的性能開銷(因?yàn)樵诰€程自旋時,始終會占用CPU的時間片裹粤,如果鎖占用的時間太長终蒂,那么自旋的線程會白白消耗掉CPU資源)。因此自旋等待的時間必須要有一定的限度遥诉,如果自選超過了限定的次數(shù)仍然沒有成功獲取到鎖拇泣,就應(yīng)該使用傳統(tǒng)的方式去掛起線程了,在JDK定義中矮锈,自旋鎖默認(rèn)的自旋次數(shù)為10次霉翔,用戶可以使用參數(shù)-XX:PreBlockSpin來更改。

偏向鎖

在大多實(shí)際環(huán)境下苞笨,鎖不僅不存在多線程競爭债朵,而且總是由同一個線程多次獲取,那么在同一個線程反復(fù)獲取所釋放鎖中猫缭,其中并還沒有鎖的競爭葱弟,那么這樣看上去壹店,多次的獲取鎖和釋放鎖帶來了很多不必要的性能開銷和上下文切換猜丹。
為了解決這一問題,HotSpot的作者在Java SE 1.6 中對Synchronized進(jìn)行了優(yōu)化硅卢,引入了偏向鎖射窒。當(dāng)一個線程訪問同步快并獲取鎖時藏杖,會在對象頭和棧幀中的鎖記錄里存儲鎖偏向的線程ID,以后該線程在進(jìn)入和推出同步塊時不需要進(jìn)行CAS操作來加鎖和解鎖脉顿。只需要簡單地測試一下對象頭的Mark Word里是否存儲著指向當(dāng)前線程的偏向鎖蝌麸。如果成功,表示線程已經(jīng)獲取到了鎖艾疟。

  • 偏向鎖的撤銷
    偏向鎖使用了一種等到競爭出現(xiàn)才釋放鎖的機(jī)制来吩,所以當(dāng)其他線程嘗試競爭偏向鎖時,持有偏向鎖的線程才會釋放鎖蔽莱。偏向鎖的撤銷需要等待擁有偏向鎖的線程到達(dá)全局安全點(diǎn)(在這個時間點(diǎn)上沒有字節(jié)碼正在執(zhí)行)弟疆,會首先暫停擁有偏向鎖的線程,然后檢查持有偏向鎖的線程是否活著盗冷,如果線程不處于活動狀態(tài)怠苔,則將鎖的對象的對象頭設(shè)置成無鎖狀態(tài),如果線程仍然活著仪糖,擁有偏向鎖的棧會被執(zhí)行(判斷是否需要持有鎖)柑司,遍歷偏向?qū)ο蟮逆i記錄,查看使用情況锅劝,如果還需要持有偏向鎖攒驰,則偏向鎖升級為輕量級鎖,如果不需要持有偏向鎖了故爵,則將鎖對象恢復(fù)成無鎖狀態(tài)讼育,最后喚醒暫停的線程。


    偏向鎖加鎖和撤銷流程
輕量級鎖
  • 加鎖
    線程在執(zhí)行同步塊之前稠集,JVM會現(xiàn)在當(dāng)前線程的棧幀中創(chuàng)建用于存儲鎖記錄的空間奶段,并將對象頭中的Mark Word復(fù)制到鎖記錄中,官方稱為Disolaced Mard Word剥纷。然后線程嘗試使用CAS將對象頭中的Mark Word替換為指向鎖記錄的指針痹籍。如果成功,當(dāng)前線程獲得鎖晦鞋,如果失敗蹲缠,表示其他線程競爭,當(dāng)前線程便嘗試使用自選來獲取鎖悠垛。

  • 解鎖
    輕量級解鎖時线定,會使用原子的CAS操作將Displaced Mard Word替換回到對象頭,如果成功确买,則表示沒有競爭發(fā)生斤讥。如果失敗,表示當(dāng)前鎖存在競爭湾趾,鎖就會膨脹成重量級鎖芭商。

爭奪鎖 導(dǎo)致的鎖膨脹流程圖

鎖的優(yōu)缺點(diǎn)對比

鎖的優(yōu)缺點(diǎn)對比

參考《深入理解JVM》《JAVA并發(fā)編程的藝術(shù)》

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末派草,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子铛楣,更是在濱河造成了極大的恐慌近迁,老刑警劉巖,帶你破解...
    沈念sama閱讀 210,914評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件簸州,死亡現(xiàn)場離奇詭異鉴竭,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)岸浑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評論 2 383
  • 文/潘曉璐 我一進(jìn)店門拓瞪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人助琐,你說我怎么就攤上這事祭埂。” “怎么了兵钮?”我有些...
    開封第一講書人閱讀 156,531評論 0 345
  • 文/不壞的土叔 我叫張陵蛆橡,是天一觀的道長。 經(jīng)常有香客問我掘譬,道長泰演,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,309評論 1 282
  • 正文 為了忘掉前任葱轩,我火速辦了婚禮睦焕,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘靴拱。我一直安慰自己垃喊,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,381評論 5 384
  • 文/花漫 我一把揭開白布袜炕。 她就那樣靜靜地躺著本谜,像睡著了一般。 火紅的嫁衣襯著肌膚如雪偎窘。 梳的紋絲不亂的頭發(fā)上乌助,一...
    開封第一講書人閱讀 49,730評論 1 289
  • 那天,我揣著相機(jī)與錄音陌知,去河邊找鬼他托。 笑死,一個胖子當(dāng)著我的面吹牛仆葡,可吹牛的內(nèi)容都是我干的赏参。 我是一名探鬼主播,決...
    沈念sama閱讀 38,882評論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼登刺!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起嗡呼,我...
    開封第一講書人閱讀 37,643評論 0 266
  • 序言:老撾萬榮一對情侶失蹤纸俭,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后南窗,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體揍很,經(jīng)...
    沈念sama閱讀 44,095評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,448評論 2 325
  • 正文 我和宋清朗相戀三年万伤,在試婚紗的時候發(fā)現(xiàn)自己被綠了窒悔。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,566評論 1 339
  • 序言:一個原本活蹦亂跳的男人離奇死亡敌买,死狀恐怖简珠,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情虹钮,我是刑警寧澤聋庵,帶...
    沈念sama閱讀 34,253評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站芙粱,受9級特大地震影響祭玉,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜春畔,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,829評論 3 312
  • 文/蒙蒙 一脱货、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧律姨,春花似錦振峻、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,715評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至缓淹,卻和暖如春哈打,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背讯壶。 一陣腳步聲響...
    開封第一講書人閱讀 31,945評論 1 264
  • 我被黑心中介騙來泰國打工料仗, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人伏蚊。 一個月前我還...
    沈念sama閱讀 46,248評論 2 360
  • 正文 我出身青樓立轧,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子氛改,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,440評論 2 348