Java鎖

工作中經(jīng)常遇到需要用鎖來(lái)控制并發(fā)的問(wèn)題,java中提供一個(gè)鎖神器關(guān)鍵字-Synchronized。通過(guò)它可以來(lái)解決多線程問(wèn)題。與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è)線程訪問(wèn)同步代碼塊時(shí),它首先是需要得到鎖才能執(zhí)行同步代碼,當(dāng)退出或者拋出異常時(shí)必須要釋放鎖,那么它是如何來(lái)實(shí)現(xiàn)這個(gè)機(jī)制的呢?我們先看一段簡(jiǎn)單的代碼:

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ì)象來(lái)實(shí)現(xiàn)方法同步和代碼塊同步,但兩者的實(shí)現(xiàn)細(xì)節(jié)不一樣。代碼塊同步是使用monitorenter和monitorexit指令實(shí)現(xiàn),而方法同步是使用另外一種方式實(shí)現(xiàn)的,細(xì)節(jié)在JVM規(guī)范里并沒(méi)有詳細(xì)說(shuō)明,而是在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ì)象沒(méi)有被鎖,或者當(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è)線程來(lái)說(shuō)是可重入的,不會(huì)出現(xiàn)自己把自己死鎖的問(wè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。

長(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ù):

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ì)象自打娘胎里出來(lái)就帶了一把看不見的鎖,它叫做內(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)前沒(méi)有任何線程擁有該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:用來(lái)實(shí)現(xiàn)重入鎖的計(jì)數(shù)。
  • HashCode:保存從對(duì)象頭拷貝過(guò)來(lái)的HashCode值(可能還包含GC age)。
  • Candidate:用來(lái)避免不必要的阻塞或等待線程喚醒,因?yàn)槊恳淮沃挥幸粋€(gè)線程能夠成功擁有鎖,如果每次前一個(gè)釋放鎖的線程喚醒所有正在阻塞或等待的線程,會(huì)引起不必要的上下文切換(從阻塞到就緒然后因?yàn)楦?jìng)爭(zhēng)鎖失敗又被阻塞)從而導(dǎo)致性能嚴(yán)重下降。Candidate只有兩種可能的值0表示沒(méi)有需要喚醒的線程1表示要喚醒一個(gè)繼任線程來(lái)競(jìng)爭(zhēng)鎖。

鎖性能優(yōu)化

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

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

鎖消除

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

鎖粗化

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

輕量級(jí)鎖

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

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

1)在代碼進(jìn)入同步塊的時(shí)候,檢查此同步對(duì)象有沒(méi)有被鎖定(鎖標(biāo)志狀態(tài)為“01”),若沒(méi)有被鎖定,虛擬機(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)前線程的棧幀,如果指向,說(shuō)明當(dāng)前線程已經(jīng)擁有這個(gè)對(duì)象的鎖,那就可以直接進(jìn)入同步塊繼續(xù)執(zhí)行,否則說(shuō)這個(gè)鎖對(duì)象已經(jīng)被其他線程搶占了。如果有兩條以上的線程爭(zhēng)用同一個(gè)鎖,那輕量級(jí)鎖就不再有效,要膨脹為重量級(jí)鎖,鎖標(biāo)志的狀態(tài)值變?yōu)椤?0”,Mark Word中存儲(chǔ)的就是指向重量級(jí)鎖(互斥量)的指針,后面等待鎖的線程也要進(jìn)入阻塞狀態(tài)

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

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

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

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

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

image.png

偏向鎖

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

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

獲取鎖

1)檢測(cè)Mark Word是否為可偏向狀態(tài),即是否為偏向鎖1,鎖標(biāo)識(shí)位為01;
2)若為可偏向狀態(tài),則測(cè)試線程ID是否為當(dāng)前線程ID,如果是,則執(zhí)行步驟(5),否則執(zhí)行步驟(3);
3)如果線程ID不為當(dāng)前線程ID,則通過(guò)CAS操作競(jìng)爭(zhēng)鎖,競(jìng)爭(zhēng)成功,則將Mark Word的線程ID替換為當(dāng)前線程ID,否則執(zhí)行線程(4);
4)通過(guò)CAS競(jìng)爭(zhēng)鎖失敗,證明當(dāng)前存在多線程競(jìng)爭(zhēng)情況,當(dāng)?shù)竭_(dá)全局安全點(diǎn),獲得偏向鎖的線程被掛起,偏向鎖升級(jí)為輕量級(jí)鎖,然后被阻塞在安全點(diǎn)的線程繼續(xù)往下執(zhí)行同步代碼塊;
5)執(zhí)行同步代碼塊

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

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

image.png

重量級(jí)鎖

重量級(jí)鎖通過(guò)對(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) 適用場(chǎng)景
偏向鎖 加鎖和解鎖不需要額外的消耗,和執(zhí)行非同步方法比僅存在納秒級(jí)的差距。 如果線程間存在鎖競(jìng)爭(zhēng),會(huì)帶來(lái)額外的鎖撤銷的消耗。 適用于只有一個(gè)線程訪問(wèn)同步塊場(chǎng)景。
輕量級(jí)鎖 競(jìng)爭(zhēng)的線程不會(huì)阻塞,提高了程序的響應(yīng)速度。 如果始終得不到鎖競(jìng)爭(zhēng)的線程使用自旋會(huì)消耗CPU。 追求響應(yīng)時(shí)間。同步塊執(zhí)行速度非常快。
重量級(jí)鎖 線程競(jìng)爭(zhēng)不使用自旋,不會(huì)消耗CPU。 線程阻塞,響應(yīng)時(shí)間緩慢。 追求吞吐量。 同步塊執(zhí)行速度較長(zhǎng)。

參考資料

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

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

?著作權(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)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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