Java鎖

工作中經(jīng)常遇到需要用鎖來控制并發(fā)的問題羊娃,java中提供一個(gè)鎖神器關(guān)鍵字-Synchronized。通過它可以來解決多線程問題。與Java中另一個(gè)Lock鎖相比接校,之前一直覺得Synchronized是重量級(jí)的鎖筹吐,很耗性能糖耸,真的是這樣嗎?

Synchronized的實(shí)現(xiàn)原理

synchronized可以保證方法或者代碼塊在運(yùn)行時(shí)丘薛,同一時(shí)刻只有一個(gè)方法可以進(jìn)入到臨界區(qū)嘉竟,同時(shí)它還可以保證共享變量的內(nèi)存可見性

同步的基礎(chǔ)

Java中每一個(gè)對(duì)象都可以作為鎖,這是synchronized實(shí)現(xiàn)同步的基礎(chǔ):

  • 普通同步方法洋侨,鎖是當(dāng)前實(shí)例對(duì)象
  • 靜態(tài)同步方法舍扰,鎖是當(dāng)前類的class對(duì)象
  • 同步方法塊,鎖是括號(hào)里面的對(duì)象

當(dāng)一個(gè)線程訪問同步代碼塊時(shí)希坚,它首先是需要得到鎖才能執(zhí)行同步代碼边苹,當(dāng)退出或者拋出異常時(shí)必須要釋放鎖,那么它是如何來實(shí)現(xiàn)這個(gè)機(jī)制的呢裁僧?我們先看一段簡單的代碼:

public class SynchronizedTest {

public synchronized void test1() {
    //do something
}

public void test2() {
    synchronized (this) {
        //do something 
    }
}
image.png

同步的原理

???JVM規(guī)范規(guī)定JVM基于進(jìn)入和退出Monitor對(duì)象來實(shí)現(xiàn)方法同步和代碼塊同步个束,但兩者的實(shí)現(xiàn)細(xì)節(jié)不一樣。代碼塊同步是使用monitorenter和monitorexit指令實(shí)現(xiàn)聊疲,而方法同步是使用另外一種方式實(shí)現(xiàn)的茬底,細(xì)節(jié)在JVM規(guī)范里并沒有詳細(xì)說明,而是在Class文件的方法表中將該方法的access_flags字段中的synchronized標(biāo)志位置1获洲,表示該方法是同步方法并使用調(diào)用該方法的對(duì)象或該方法所屬的Class在JVM的內(nèi)部對(duì)象表示Class做為鎖對(duì)象阱表。

???monitorenter指令是在編譯后插入到同步代碼塊的開始位置,而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ì)象的鎖蒜鸡。

???在虛擬機(jī)規(guī)范的要求,在執(zhí)行monitorenter指令時(shí)牢裳,首先嘗試獲取對(duì)象的鎖逢防,如果這個(gè)對(duì)象沒有被鎖,或者當(dāng)前線程已經(jīng)擁有這個(gè)對(duì)象的鎖蒲讯,把鎖的計(jì)數(shù)器加1忘朝,相應(yīng)的,在執(zhí)行monitorexit指令時(shí)會(huì)將計(jì)數(shù)器減1判帮,當(dāng)計(jì)數(shù)器為0是局嘁,鎖被釋放溉箕。如果獲取對(duì)象的鎖失敗,那當(dāng)前線程就要阻塞等待悦昵,直到對(duì)象鎖被另外一個(gè)線程釋放為止肴茄。

???在虛擬機(jī)規(guī)范對(duì)monitorenter和monitorexit的行為描述中,有兩點(diǎn)需要特別注意的但指。首先寡痰,synchronized同步塊對(duì)同一個(gè)線程來說是可重入的,不會(huì)出現(xiàn)自己把自己死鎖的問題棋凳。其次拦坠,同步塊在已進(jìn)入的線程執(zhí)行完之前,會(huì)阻塞后面其他線程的進(jìn)入

Java對(duì)象頭

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

長度 內(nèi)容 說明
32/64bit Mark Word 存儲(chǔ)對(duì)象的hashCode或鎖信息等。
32/64bit Class Metadata Address 存儲(chǔ)到對(duì)象類型數(shù)據(jù)的指針
32/64bit Array length 數(shù)組的長度(如果當(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)志位
無鎖狀態(tài) 對(duì)象的hashCode 對(duì)象分代年齡 0 01

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

image.png

在64位虛擬機(jī)下忙芒,Mark Word是64bit大小的示弓,其存儲(chǔ)結(jié)構(gòu)如下:


image.png

Monitor

???什么是Monitor?我們可以把它理解為一個(gè)同步工具呵萨,也可以描述為一種同步機(jī)制奏属,它通常被描述為一個(gè)對(duì)象。

???與一切皆對(duì)象一樣潮峦,所有的Java對(duì)象是天生的Monitor囱皿,每一個(gè)Java對(duì)象都有成為Monitor的潛質(zhì),因?yàn)樵贘ava的設(shè)計(jì)中 忱嘹,每一個(gè)Java對(duì)象自打娘胎里出來就帶了一把看不見的鎖嘱腥,它叫做內(nèi)部鎖或者M(jìn)onitor鎖。
Monitor 是線程私有的數(shù)據(jù)結(jié)構(gòu)拘悦,每一個(gè)線程都有一個(gè)可用monitor record列表齿兔,同時(shí)還有一個(gè)全局的可用列表。每一個(gè)被鎖住的對(duì)象都會(huì)和一個(gè)monitor關(guān)聯(lián)(對(duì)象頭的MarkWord中的LockWord指向monitor的起始地址),同時(shí)monitor中有一個(gè)Owner字段存放擁有該鎖的線程的唯一標(biāo)識(shí)分苇,表示該鎖被這個(gè)線程占用添诉。其結(jié)構(gòu)如下:


image.png
  • Owner:初始時(shí)為NULL表示當(dāng)前沒有任何線程擁有該monitor record,當(dāng)線程成功擁有該鎖后保存線程唯一標(biāo)識(shí)组砚,當(dāng)鎖被釋放時(shí)又設(shè)置為NULL吻商;
  • EntryQ:關(guān)聯(lián)一個(gè)系統(tǒng)互斥鎖(semaphore)掏颊,阻塞所有試圖鎖住monitor record失敗的線程糟红。
  • RcThis:表示blocked或waiting在該monitor record上的所有線程的個(gè)數(shù)。
  • Nest:用來實(shí)現(xiàn)重入鎖的計(jì)數(shù)乌叶。
  • HashCode:保存從對(duì)象頭拷貝過來的HashCode值(可能還包含GC age)盆偿。
  • Candidate:用來避免不必要的阻塞或等待線程喚醒,因?yàn)槊恳淮沃挥幸粋€(gè)線程能夠成功擁有鎖准浴,如果每次前一個(gè)釋放鎖的線程喚醒所有正在阻塞或等待的線程事扭,會(huì)引起不必要的上下文切換(從阻塞到就緒然后因?yàn)楦偁庢i失敗又被阻塞)從而導(dǎo)致性能嚴(yán)重下降。Candidate只有兩種可能的值0表示沒有需要喚醒的線程1表示要喚醒一個(gè)繼任線程來競爭鎖乐横。

鎖性能優(yōu)化

自旋鎖與自適應(yīng)自旋

???如果物理機(jī)有超過一個(gè)以上的處理器求橄,讓后面請(qǐng)求鎖的那個(gè)線程“稍等一下”,但不放棄處理器的執(zhí)行時(shí)間葡公,看看持有鎖的線程是否很快就會(huì)釋放鎖罐农。為了讓線程等待,我們只需讓線程執(zhí)行一個(gè)忙循環(huán)(自旋)催什,這項(xiàng)技術(shù)就是所謂的自旋鎖
自旋次數(shù)默認(rèn)是10次涵亏,參數(shù)-XX:PreBlockSpin來更改
在JDK1.6中引入了自適應(yīng)的自旋鎖。自適應(yīng)意味著自旋的時(shí)間不再固定蒲凶,而是由前一次在同一個(gè)鎖上的自旋時(shí)間及鎖的擁有者的

鎖消除

???虛擬機(jī)即時(shí)編譯器在運(yùn)行時(shí)气筋,對(duì)一些代碼上要求同步,但是被檢測到不可能存在共享數(shù)據(jù)競爭的鎖進(jìn)行消除旋圆。鎖消除的主要判斷依據(jù)來源于逃逸分析的數(shù)據(jù)支撐宠默,如果判斷在一段代碼中,堆上的所有數(shù)據(jù)都不會(huì)逃逸出去從而被其他線程鎖訪問到灵巧,那就可以把它們當(dāng)做棧上數(shù)據(jù)對(duì)待搀矫,認(rèn)為它們是線程私有的,無須加鎖

鎖粗化

???如果一系統(tǒng)的連續(xù)操作都對(duì)同一個(gè)對(duì)象反復(fù)加鎖和解鎖孩等,甚至加鎖操作是出現(xiàn)在循環(huán)體中艾君,那即使沒有線程競爭,頻繁地進(jìn)行互斥同步操作也會(huì)導(dǎo)致不必要的性能損耗肄方,如果虛擬機(jī)探測到有這樣一串零碎的操作都對(duì)同一個(gè)對(duì)象加鎖冰垄,將會(huì)把加鎖同步的范圍擴(kuò)展(粗化)到整個(gè)操作序列的外部

輕量級(jí)鎖

???輕量級(jí)鎖時(shí)JDK1.6之后加入的新型鎖機(jī)制,它名字中的“輕量級(jí)”是相對(duì)于使用操作系統(tǒng)互斥量來實(shí)現(xiàn)的傳統(tǒng)鎖而言的,因此傳統(tǒng)的鎖機(jī)制就稱為“重量級(jí)”鎖虹茶。首先需要強(qiáng)調(diào)一點(diǎn)的是逝薪,輕量級(jí)鎖并不是用來代替重量級(jí)鎖的,它的本意是在沒有多線程競爭的前提下蝴罪,減少傳統(tǒng)的重量級(jí)鎖使用操作系統(tǒng)互斥量產(chǎn)生的性能消耗董济。

獲取輕量級(jí)鎖步驟:

1)在代碼進(jìn)入同步塊的時(shí)候,檢查此同步對(duì)象有沒有被鎖定(鎖標(biāo)志狀態(tài)為“01”)要门,若沒有被鎖定虏肾,虛擬機(jī)首先將在當(dāng)前線程的棧幀中建立一個(gè)名為鎖記錄(Lock Record)的空間,用于存儲(chǔ)鎖對(duì)象目前的Mark Word的拷貝(官方把這份拷貝加了一個(gè)Displaced前綴欢搜,即Displaced Mark Word)封豪,否則執(zhí)行步驟3)

2)虛擬機(jī)將使用CAS操作嘗試將對(duì)象的Mark Word更新為指向Lock Record的指針,如果這個(gè)動(dòng)作更新成功炒瘟,那么這個(gè)線程就擁有了該對(duì)象的鎖吹埠,并且對(duì)象Mark Word的鎖標(biāo)志位(Mark Word的最后2bit)將轉(zhuǎn)變?yōu)椤?0”,即表示此對(duì)象處于輕量級(jí)鎖定狀態(tài)疮装,否則執(zhí)行步驟3)

3)虛擬機(jī)首先檢查對(duì)象的Mark Word是否指向當(dāng)前線程的棧幀缘琅,如果指向,說明當(dāng)前線程已經(jīng)擁有這個(gè)對(duì)象的鎖廓推,那就可以直接進(jìn)入同步塊繼續(xù)執(zhí)行刷袍,否則說這個(gè)鎖對(duì)象已經(jīng)被其他線程搶占了。如果有兩條以上的線程爭用同一個(gè)鎖受啥,那輕量級(jí)鎖就不再有效做个,要膨脹為重量級(jí)鎖,鎖標(biāo)志的狀態(tài)值變?yōu)椤?0”滚局,Mark Word中存儲(chǔ)的就是指向重量級(jí)鎖(互斥量)的指針居暖,后面等待鎖的線程也要進(jìn)入阻塞狀態(tài)

解除輕量級(jí)鎖步驟:

輕量級(jí)鎖的釋放也是通過CAS操作來進(jìn)行的,主要步驟如下:

1)取出在獲取輕量級(jí)鎖保存在Displaced Mark Word中的數(shù)據(jù)藤肢;

2)用CAS操作將取出的數(shù)據(jù)替換當(dāng)前對(duì)象的Mark
Word中太闺,如果成功,則說明釋放鎖成功嘁圈,否則執(zhí)行(3)省骂;

3)如果CAS操作替換失敗,說明有其他線程嘗試獲取該鎖最住,則需要在釋放鎖的同時(shí)需要喚醒被掛起的線程钞澳。

image.png

偏向鎖

???偏向鎖也是JDK1.6中引入的一項(xiàng)鎖優(yōu)化,它的目的是消除數(shù)據(jù)在無競爭情況下的同步原語涨缚,進(jìn)一步提高程序的運(yùn)行性能轧粟。如果說輕量級(jí)鎖是在無競爭的情況下使用CAS操作去消除同步使用的互斥量,那偏向鎖就是在無競爭的情況下把整個(gè)同步都消除了,連CAS操作都不做了

???偏向鎖的“偏”兰吟,就是偏心的“偏”通惫、偏袒的“偏”,它的意思是這個(gè)鎖會(huì)偏向于第一個(gè)獲得它的線程混蔼,如果在接下來的執(zhí)行過程中履腋,該鎖沒有被其他的線程獲取,則持有偏向鎖的線程將永遠(yuǎn)不需要再進(jìn)行同步惭嚣。

獲取鎖

1)檢測Mark Word是否為可偏向狀態(tài)遵湖,即是否為偏向鎖1,鎖標(biāo)識(shí)位為01料按;
2)若為可偏向狀態(tài)奄侠,則測試線程ID是否為當(dāng)前線程ID,如果是载矿,則執(zhí)行步驟(5),否則執(zhí)行步驟(3)烹卒;
3)如果線程ID不為當(dāng)前線程ID闷盔,則通過CAS操作競爭鎖,競爭成功旅急,則將Mark Word的線程ID替換為當(dāng)前線程ID逢勾,否則執(zhí)行線程(4);
4)通過CAS競爭鎖失敗藐吮,證明當(dāng)前存在多線程競爭情況溺拱,當(dāng)?shù)竭_(dá)全局安全點(diǎn),獲得偏向鎖的線程被掛起谣辞,偏向鎖升級(jí)為輕量級(jí)鎖迫摔,然后被阻塞在安全點(diǎn)的線程繼續(xù)往下執(zhí)行同步代碼塊;
5)執(zhí)行同步代碼塊

釋放鎖
???偏向鎖的釋放采用了一種只有競爭才會(huì)釋放鎖的機(jī)制泥从,線程是不會(huì)主動(dòng)去釋放偏向鎖句占,需要等待其他線程來競爭。偏向鎖的撤銷需要等待全局安全點(diǎn)(這個(gè)時(shí)間點(diǎn)是上沒有正在執(zhí)行的代碼)躯嫉。其步驟如下:

1)暫停擁有偏向鎖的線程纱烘,判斷鎖對(duì)象石是否還處于被鎖定狀態(tài);
2)撤銷偏向蘇祈餐,恢復(fù)到無鎖狀態(tài)(01)或者輕量級(jí)鎖的狀態(tài)擂啥;

image.png

重量級(jí)鎖

重量級(jí)鎖通過對(duì)象內(nèi)部的監(jiān)視器(monitor)實(shí)現(xiàn),其中monitor的本質(zhì)是依賴于底層操作系統(tǒng)的Mutex Lock實(shí)現(xiàn)帆阳,操作系統(tǒng)實(shí)現(xiàn)線程之間的切換需要從用戶態(tài)到內(nèi)核態(tài)的切換哺壶,切換成本非常高。

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

優(yōu)點(diǎn) 缺點(diǎn) 適用場景
偏向鎖 加鎖和解鎖不需要額外的消耗,和執(zhí)行非同步方法比僅存在納秒級(jí)的差距变骡。 如果線程間存在鎖競爭离赫,會(huì)帶來額外的鎖撤銷的消耗。 適用于只有一個(gè)線程訪問同步塊場景塌碌。
輕量級(jí)鎖 競爭的線程不會(huì)阻塞渊胸,提高了程序的響應(yīng)速度。 如果始終得不到鎖競爭的線程使用自旋會(huì)消耗CPU台妆。 追求響應(yīng)時(shí)間翎猛。同步塊執(zhí)行速度非常快接剩。
重量級(jí)鎖 線程競爭不使用自旋切厘,不會(huì)消耗CPU。 線程阻塞懊缺,響應(yīng)時(shí)間緩慢疫稿。 追求吞吐量。 同步塊執(zhí)行速度較長鹃两。

參考資料

周志明:《深入理解Java虛擬機(jī)》

方騰飛:《Java并發(fā)編程的藝術(shù)》

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末遗座,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子俊扳,更是在濱河造成了極大的恐慌途蒋,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,194評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件馋记,死亡現(xiàn)場離奇詭異号坡,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)梯醒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門宽堆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人冤馏,你說我怎么就攤上這事日麸。” “怎么了逮光?”我有些...
    開封第一講書人閱讀 156,780評(píng)論 0 346
  • 文/不壞的土叔 我叫張陵代箭,是天一觀的道長。 經(jīng)常有香客問我涕刚,道長嗡综,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,388評(píng)論 1 283
  • 正文 為了忘掉前任杜漠,我火速辦了婚禮极景,結(jié)果婚禮上察净,老公的妹妹穿的比我還像新娘。我一直安慰自己盼樟,他們只是感情好氢卡,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,430評(píng)論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著晨缴,像睡著了一般译秦。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上击碗,一...
    開封第一講書人閱讀 49,764評(píng)論 1 290
  • 那天筑悴,我揣著相機(jī)與錄音,去河邊找鬼稍途。 笑死阁吝,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的械拍。 我是一名探鬼主播突勇,決...
    沈念sama閱讀 38,907評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼殊者!你這毒婦竟也來了与境?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,679評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤猖吴,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后挥转,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體海蔽,經(jīng)...
    沈念sama閱讀 44,122評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,459評(píng)論 2 325
  • 正文 我和宋清朗相戀三年绑谣,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了党窜。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,605評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡借宵,死狀恐怖幌衣,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情壤玫,我是刑警寧澤豁护,帶...
    沈念sama閱讀 34,270評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站欲间,受9級(jí)特大地震影響楚里,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜猎贴,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,867評(píng)論 3 312
  • 文/蒙蒙 一班缎、第九天 我趴在偏房一處隱蔽的房頂上張望蝴光。 院中可真熱鬧,春花似錦达址、人聲如沸蔑祟。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽疆虚。三九已至,卻和暖如春右冻,著一層夾襖步出監(jiān)牢的瞬間装蓬,已是汗流浹背控漠。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評(píng)論 1 265
  • 我被黑心中介騙來泰國打工迅腔, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人安疗。 一個(gè)月前我還...
    沈念sama閱讀 46,297評(píng)論 2 360
  • 正文 我出身青樓乳蛾,卻偏偏與公主長得像暗赶,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子肃叶,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,472評(píng)論 2 348

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