Java Synchronized原理

在多線程并發(fā)編程中Synchronized一直是元老級(jí)角色,很多人都會(huì)稱呼它為重量級(jí)鎖,但是隨著Java SE1.6對(duì)Synchronized進(jìn)行了各種優(yōu)化之后,有些情況下它并不那么重了,本文詳細(xì)介紹了Java SE1.6中為了減少獲得鎖和釋放鎖帶來(lái)的性能消耗而引入的偏向鎖和輕量級(jí)鎖,以及鎖的存儲(chǔ)結(jié)構(gòu)和升級(jí)過(guò)程。

CAS(Compare and Swap),用于在硬件層面上提供原子性操作。在 Intel 處理器中,比較并交換通過(guò)指令cmpxchg實(shí)現(xiàn)。比較是否和給定的數(shù)值一致,如果一致則修改,不一致則不修改。

基礎(chǔ)

Java中的每一個(gè)對(duì)象都可以作為鎖。

  • 對(duì)于同步方法,鎖是當(dāng)前實(shí)例對(duì)象。
  • 對(duì)于靜態(tài)同步方法,鎖是當(dāng)前對(duì)象的Class對(duì)象。
  • 對(duì)于同步方法塊,鎖是Synchonized括號(hào)里配置的對(duì)象。

當(dāng)一個(gè)線程試圖訪問(wèn)同步代碼塊時(shí),它首先必須得到鎖,退出或拋出異常時(shí)必須釋放鎖。那么鎖存在哪里呢?鎖里面會(huì)存儲(chǔ)什么信息呢?

同步的原理

JVM規(guī)范規(guī)定JVM基于進(jìn)入和退出 Monitor 對(duì)象來(lái)實(shí)現(xiàn)方法同步和代碼塊同步,但兩者的實(shí)現(xiàn)細(xì)節(jié)不一樣。代碼塊同步是使用monitorentermonitorexit指令實(shí)現(xiàn),而方法同步是使用另外一種方式實(shí)現(xiàn)的,細(xì)節(jié)在JVM規(guī)范里并沒(méi)有詳細(xì)說(shuō)明,但是方法的同步同樣可以使用這兩個(gè)指令來(lái)實(shí)現(xiàn)。

monitorenter指令是在編譯后插入到同步代碼塊的開(kāi)始位置,而monitorexit是插入到方法結(jié)束處和異常處,JVM要保證每個(gè)monitorenter必須有對(duì)應(yīng)的monitorexit與之配對(duì)。任何對(duì)象都有一個(gè)monitor與之關(guān)聯(lián),當(dāng)且一個(gè)monitor被持有后,它將處于鎖定狀態(tài)。線程執(zhí)行到 monitorenter 指令時(shí),將會(huì)嘗試獲取對(duì)象所對(duì)應(yīng)的 monitor 的所有權(quán),即嘗試獲得對(duì)象的鎖。

Java對(duì)象頭

鎖存在Java對(duì)象頭里。如果對(duì)象是數(shù)組類型,則虛擬機(jī)用3個(gè)Word(字寬)存儲(chǔ)對(duì)象頭,如果對(duì)象是非數(shù)組類型,則用2字寬存儲(chǔ)對(duì)象頭。在32位虛擬機(jī)中,一字寬等于四字節(jié),即32bit。

| 長(zhǎng)度 | 內(nèi)容 | 說(shuō)明 |
| :------------- | :------------- |
|32/64bit| Mark Word |存儲(chǔ)對(duì)象的hashCode或鎖信息等|
|32/64bit| Class Metadata Address |存儲(chǔ)到對(duì)象類型數(shù)據(jù)的指針|
|32/64bit| Array length |數(shù)組的長(zhǎng)度(如果當(dāng)前對(duì)象是數(shù)組)|

Java對(duì)象頭里的Mark Word里默認(rèn)存儲(chǔ)對(duì)象的HashCode,分代年齡和鎖標(biāo)記位。32位JVM的Mark Word的默認(rèn)存儲(chǔ)結(jié)構(gòu)如下:

| | 25 bit |4bit|1bit是否是偏向鎖|2bit鎖標(biāo)志位|
| :------------- | :------------- |
|無(wú)鎖狀態(tài)| 對(duì)象的hashCode| 對(duì)象分代年齡| 0| 01|

在運(yùn)行期間Mark Word里存儲(chǔ)的數(shù)據(jù)會(huì)隨著鎖標(biāo)志位的變化而變化。Mark Word可能變化為存儲(chǔ)以下4種數(shù)據(jù):

synchronized_1.png

鎖的升級(jí)

Java SE1.6為了減少獲得鎖和釋放鎖所帶來(lái)的性能消耗,引入了“偏向鎖”和“輕量級(jí)鎖”,所以在Java SE1.6里鎖一共有四種狀態(tài),無(wú)鎖狀態(tài),偏向鎖狀態(tài),輕量級(jí)鎖狀態(tài)重量級(jí)鎖狀態(tài),它會(huì)隨著競(jìng)爭(zhēng)情況逐漸升級(jí)。鎖可以升級(jí)但不能降級(jí),意味著偏向鎖升級(jí)成輕量級(jí)鎖后不能降級(jí)成偏向鎖。這種鎖升級(jí)卻不能降級(jí)的策略,目的是為了提高獲得鎖和釋放鎖的效率。\

線程對(duì)鎖的競(jìng)爭(zhēng)

當(dāng)多個(gè)線程同時(shí)請(qǐng)求某個(gè)對(duì)象監(jiān)視器時(shí),對(duì)象監(jiān)視器會(huì)設(shè)置幾種狀態(tài)用來(lái)區(qū)分請(qǐng)求的線程:

  • Contention List:所有請(qǐng)求鎖的線程將被首先放置到該競(jìng)爭(zhēng)隊(duì)列
  • Entry List:Contention List中那些有資格成為候選人的線程被移到Entry List
  • Wait Set:那些調(diào)用wait方法被阻塞的線程被放置到Wait Set
  • OnDeck:任何時(shí)刻最多只能有一個(gè)線程正在競(jìng)爭(zhēng)鎖,該線程稱為OnDeck
  • Owner:獲得鎖的線程稱為Owner
  • !Owner:釋放鎖的線程
synchronized_2.gif

新請(qǐng)求鎖的線程將首先被加入到ConetentionList中,當(dāng)某個(gè)擁有鎖的線程(Owner狀態(tài))調(diào)用unlock之后,如果發(fā)現(xiàn)EntryList為空則從ContentionList中移動(dòng)線程到EntryList,下面說(shuō)明下ContentionListEntryList的實(shí)現(xiàn)方式:

ContentionList

ContentionList并不是一個(gè)真正的Queue,而只是一個(gè)虛擬隊(duì)列,原因在于ContentionList是由Node及其next指針邏輯構(gòu)成,并不存在一個(gè)Queue的數(shù)據(jù)結(jié)構(gòu)。ContentionList是一個(gè)后進(jìn)先出(LIFO)的隊(duì)列,每次新加入Node時(shí)都會(huì)在隊(duì)頭進(jìn)行,通過(guò)CAS改變第一個(gè)節(jié)點(diǎn)的的指針為新增節(jié)點(diǎn),同時(shí)設(shè)置新增節(jié)點(diǎn)的next指向后續(xù)節(jié)點(diǎn),而取得操作則發(fā)生在隊(duì)尾。顯然,該結(jié)構(gòu)其實(shí)是個(gè)Lock-Free的隊(duì)列。

因?yàn)橹挥蠴wner線程才能從隊(duì)尾取元素,也即線程出列操作無(wú)爭(zhēng)用,當(dāng)然也就避免了CAS的ABA問(wèn)題。

EntryList

EntryListContentionList邏輯上同屬等待隊(duì)列,ContentionList會(huì)被線程并發(fā)訪問(wèn),為了降低對(duì)ContentionList隊(duì)尾的爭(zhēng)用,而建立EntryListOwner線程在unlock時(shí)會(huì)從ContentionList中遷移線程到EntryList,并會(huì)指定EntryList中的某個(gè)線程(一般為Head)為Ready(OnDeck)線程。Owner線程并不是把鎖傳遞給OnDeck線程,只是把競(jìng)爭(zhēng)鎖的權(quán)利交給OnDeck,OnDeck線程需要重新競(jìng)爭(zhēng)鎖。這樣做雖然犧牲了一定的公平性,但極大的提高了整體吞吐量,在Hotspot中把OnDeck的選擇行為稱之為“競(jìng)爭(zhēng)切換”。

OnDeck線程獲得鎖后即變?yōu)閛wner線程,無(wú)法獲得鎖則會(huì)依然留在EntryList中,考慮到公平性,在EntryList中的位置不發(fā)生變化(依然在隊(duì)頭)。如果Owner線程被wait方法阻塞,則轉(zhuǎn)移到WaitSet隊(duì)列;如果在某個(gè)時(shí)刻被notify/notifyAll喚醒,則再次轉(zhuǎn)移到EntryList。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Java8張圖 11、字符串不變性 12、equals()方法、hashCode()方法的區(qū)別 13、...
    Miley_MOJIE閱讀 3,896評(píng)論 0 11
  • 一、多線程 說(shuō)明下線程的狀態(tài) java中的線程一共有 5 種狀態(tài)。 NEW:這種情況指的是,通過(guò) New 關(guān)鍵字創(chuàng)...
    Java旅行者閱讀 4,865評(píng)論 0 44
  • 取名高大上,省級(jí)課題,學(xué)科基地,名校展示,名師引領(lǐng),召集大市老師旁聽(tīng)學(xué)習(xí)。本是萬(wàn)分期待,結(jié)果卻是億萬(wàn)傷悲...
    源味清風(fēng)閱讀 303評(píng)論 0 0
  • 龍華市 人馬車龍,高樓大廈,四面環(huán)顧高山,城里四通八達(dá),是華夏經(jīng)濟(jì)最為富裕的地方之一。 龍華第一中學(xué)便是華夏出了名...
    九天攬?jiān)耞2584閱讀 750評(píng)論 1 1
  • 工作了一年,是不是盤(pán)算著這一年攢了多少錢(qián); 看了一眼賣(mài)房廣告,是不是心里算著還有多久可以攢足首付; 同事興高采烈計(jì)...
    攀峰說(shuō)閱讀 381評(píng)論 0 0

友情鏈接更多精彩內(nèi)容