- Java并發(fā)編程深度解析與實(shí)戰(zhàn)
- 譚鋒(Mic)
- 5720字
- 2022-05-10 18:39:18
2.7 鎖升級(jí)的實(shí)現(xiàn)流程
在synchronized中引入偏向鎖、輕量級(jí)鎖、重量級(jí)鎖之后,當(dāng)前具體會(huì)用到synchronized中的哪種類型鎖,是根據(jù)線程的競(jìng)爭(zhēng)激烈程度來(lái)決定的,這個(gè)過(guò)程我們稱之為鎖的升級(jí),具體的升級(jí)流程如圖2-18所示。

圖2-18 synchronized鎖的升級(jí)流程
當(dāng)一個(gè)線程訪問(wèn)增加了synchronized關(guān)鍵字的代碼塊時(shí),如果偏向鎖是開啟狀態(tài),則先嘗試通過(guò)偏向鎖來(lái)獲得鎖資源,這個(gè)過(guò)程僅僅通過(guò)CAS來(lái)完成。如果當(dāng)前已經(jīng)有其他線程獲得了偏向鎖,那么搶占鎖資源的線程由于無(wú)法獲得鎖,所以會(huì)嘗試升級(jí)到輕量級(jí)鎖來(lái)進(jìn)行鎖資源搶占,輕量級(jí)鎖就是通過(guò)多次CAS(也就是自旋鎖)來(lái)完成的。如果這個(gè)線程通過(guò)多次自旋仍然無(wú)法獲得鎖資源,那么最終只能升級(jí)到重量級(jí)鎖來(lái)實(shí)現(xiàn)線程的等待。
為了更清晰地理解鎖的升級(jí)流程,下面針對(duì)鎖升級(jí)的過(guò)程及鎖資源競(jìng)爭(zhēng)的原理做一個(gè)更詳細(xì)的分析。
注意:本章內(nèi)容是基于Hotspot 1.8中的bytecodeInterpreter.cpp、biasedLocking.cpp、synchronizer.cpp、objectMonitor.hpp、markOop.hpp等源碼的實(shí)現(xiàn)分析。
2.7.1 偏向鎖的實(shí)現(xiàn)原理
偏向鎖的實(shí)現(xiàn)原理比較簡(jiǎn)單,就是使用CAS機(jī)制來(lái)替換對(duì)象頭中的Thread Id,如果成功,則獲得偏向鎖,否則,就會(huì)升級(jí)到輕量級(jí)鎖。它的具體實(shí)現(xiàn)流程如圖2-19所示,圖比較長(zhǎng),建議讀者結(jié)合文字解析一起看,方便理解。
2.7.1.1 獲取偏向鎖的流程
圖2-19僅代表獲取偏向鎖的粗粒度流程圖,整體的流程是基于Hotspot 1.8版本的源碼實(shí)現(xiàn)來(lái)建立的,主要針對(duì)核心節(jié)點(diǎn)做了相對(duì)詳細(xì)的說(shuō)明,便于大家解讀,以下是獲取偏向鎖的過(guò)程講解。
注意:偏向鎖是在沒(méi)有線程競(jìng)爭(zhēng)的情況下實(shí)現(xiàn)的一種鎖,不能排除存在鎖競(jìng)爭(zhēng)的情況,所以偏向鎖的獲取有兩種情況。
沒(méi)有鎖競(jìng)爭(zhēng)
在沒(méi)有鎖競(jìng)爭(zhēng)并且開啟了偏向鎖的情況下,當(dāng)線程1訪問(wèn)synchronized(lock)修飾的代碼塊時(shí):
? 從當(dāng)前線程的棧中找到一個(gè)空閑的BasicObjectLock(在圖2-19中稱為L(zhǎng)ock Record),它是一個(gè)基礎(chǔ)的鎖對(duì)象,在后續(xù)的輕量級(jí)鎖和重量級(jí)鎖中都會(huì)用到,BasicObjectLock包含以下兩個(gè)屬性。
○ BasicLock,該屬性中有一個(gè)字段markOop,用于保存指向lock鎖對(duì)象的對(duì)象頭數(shù)據(jù)。
○ oop,指向lock鎖對(duì)象的指針。
注意:Lock Record是線程私有的數(shù)據(jù)結(jié)構(gòu),每個(gè)線程都有一個(gè)可用的Lock Record列表,并且每一個(gè)LockRecord都會(huì)關(guān)聯(lián)到鎖對(duì)象lock的Mark Word。
? 將BasicObjectLock中的oop指針指向當(dāng)前的鎖對(duì)象lock。
? 獲得當(dāng)前鎖對(duì)象lock的對(duì)象頭,通過(guò)對(duì)象頭來(lái)判斷是否可偏向,也就是說(shuō)鎖標(biāo)記為101,并且Thread Id為空。
○ 如果為可偏向狀態(tài),那么判斷當(dāng)前偏向的線程是不是線程1,如果偏向的是自己,則不需要再搶占鎖,直接有資格運(yùn)行同步代碼塊。
○ 如果為不可偏向狀態(tài),則需要通過(guò)輕量級(jí)鎖來(lái)完成鎖的搶占過(guò)程。

圖2-19 偏向鎖的獲取流程
? 如果對(duì)象鎖lock偏向其他線程或者當(dāng)前是匿名偏向狀態(tài)(也就是沒(méi)有偏向任何一個(gè)線程),則先構(gòu)建一個(gè)匿名偏向的Mark Word,然后通過(guò)CAS方法,把一個(gè)匿名偏向的Mark Word修改為偏向線程1。如果當(dāng)前鎖對(duì)象lock已經(jīng)偏向了其他線程,那么CAS一定會(huì)失敗。
存在鎖競(jìng)爭(zhēng)
假設(shè)線程1獲得了偏向鎖,此時(shí)線程2去執(zhí)行synchronized(lock)同步代碼塊,如果訪問(wèn)到同一個(gè)對(duì)象鎖則會(huì)觸發(fā)鎖競(jìng)爭(zhēng)并觸發(fā)偏向鎖撤銷,撤銷流程如下。
第一步,線程2調(diào)用撤銷偏向鎖方法,嘗試撤銷lock鎖對(duì)象的偏向鎖。
第二步,撤銷偏向鎖需要到達(dá)全局安全點(diǎn)(SafePoint)才會(huì)執(zhí)行,全局安全點(diǎn)就是當(dāng)前線程運(yùn)行到的這個(gè)位置,線程的狀態(tài)可以被確定,堆對(duì)象的狀態(tài)也是確定的,在這個(gè)位置JVM可以安全地進(jìn)行GC、偏向鎖撤銷等動(dòng)作。當(dāng)?shù)竭_(dá)全局安全點(diǎn)后,會(huì)暫停獲得偏向鎖的線程1。
第三步,檢查獲得偏向鎖的線程1的狀態(tài),這里存在兩種狀態(tài)。
? 線程1已經(jīng)執(zhí)行完同步代碼塊或者處于非存活狀態(tài)。在這種情況下,直接把偏向鎖撤銷恢復(fù)成無(wú)鎖狀態(tài),然后線程2升級(jí)到輕量級(jí)鎖,通過(guò)輕量級(jí)鎖搶占鎖資源(輕量級(jí)鎖的邏輯在后面會(huì)分析)。
? 線程1還在執(zhí)行同步代碼塊中的指令,也就是說(shuō)沒(méi)有退出同步代碼塊。在這種情況下,直接把鎖對(duì)象lock升級(jí)成輕量級(jí)鎖(由于這里是全局安全點(diǎn),所以不需要通過(guò)CAS來(lái)實(shí)現(xiàn)),并且指向線程1,表示線程1持有輕量級(jí)鎖,接著線程1繼續(xù)執(zhí)行同步代碼塊中的代碼。
至此,偏向鎖的搶占邏輯和偏向鎖的撤銷邏輯就分析完了,讀者可以對(duì)照?qǐng)D2-19進(jìn)行梳理,在源碼中偏向鎖還有很多的邏輯,比如批量撤銷、批量重偏向、撤銷并重偏向等,有興趣的讀者可以下載相關(guān)源碼去分析。
2.7.1.2 偏向鎖的釋放
在偏向鎖執(zhí)行完synchronized同步代碼塊后,會(huì)觸發(fā)偏向鎖釋放的流程,需要注意的是,偏向鎖本質(zhì)上并沒(méi)有釋放,因?yàn)楫?dāng)前鎖對(duì)象lock仍然是偏向該線程的。
從源碼來(lái)看,釋放的過(guò)程只是把Lock Record釋放了,也就是說(shuō)把Lock Record保存的鎖對(duì)象的Mark Word設(shè)置為空。
2.7.1.3 偏向鎖批量重偏向
當(dāng)一個(gè)鎖對(duì)象lock只被同一個(gè)線程訪問(wèn)時(shí),該鎖對(duì)象的鎖狀態(tài)就是偏向鎖,并且一直偏向該線程。當(dāng)有任何一個(gè)線程來(lái)訪問(wèn)該鎖對(duì)象lock時(shí),不管之前獲得偏向鎖線程的狀態(tài)是存活還是死亡,lock鎖對(duì)象都會(huì)升級(jí)為輕量級(jí)鎖,并且鎖在升級(jí)之后是不可逆的。
假設(shè)一個(gè)線程t1針對(duì)大量的鎖對(duì)象增加了偏向鎖,之后線程t2來(lái)訪問(wèn)這些鎖對(duì)象,在不考慮鎖競(jìng)爭(zhēng)的情況下,需要對(duì)之前所有偏向線程t1的鎖對(duì)象進(jìn)行偏向鎖撤銷和升級(jí),這個(gè)過(guò)程比較耗時(shí),而且虛擬機(jī)會(huì)認(rèn)為這個(gè)鎖不適合再偏向于原來(lái)的t1線程,于是當(dāng)偏向鎖撤銷次數(shù)達(dá)到20次時(shí),會(huì)觸發(fā)批量重偏向,把所有的鎖對(duì)象全部偏向線程t2。
偏向鎖撤銷并批量重偏向的觸發(fā)閾值可以通過(guò)XX:BiasedLockingBulkRebiasThreshold = 20來(lái)配置,默認(rèn)是20。下面的代碼演示了批量重偏向的實(shí)現(xiàn)。


注意,在這個(gè)案例中,偏向鎖的延時(shí)啟動(dòng)設(shè)置為0:XX:BiasedLockingStartupDelay=0。
代碼分析如下:
首先,在t1線程中,創(chuàng)建100個(gè)鎖對(duì)象BulkRevokeExample,并且對(duì)每個(gè)對(duì)象都增加了偏向鎖,這100個(gè)鎖對(duì)象都偏向t1線程。下面的內(nèi)容打印了第20個(gè)鎖對(duì)象的對(duì)象頭,鎖標(biāo)記為[101],表示偏向鎖狀態(tài)。

然后,t2線程嘗試競(jìng)爭(zhēng)鎖,對(duì)t1線程中加了偏向鎖的鎖對(duì)象觸發(fā)撤銷并重偏向。理論上來(lái)說(shuō),t2線程需要對(duì)每個(gè)鎖對(duì)象的對(duì)象頭通過(guò)CAS升級(jí)到輕量級(jí)鎖,我們先來(lái)看一下打印結(jié)果。


在運(yùn)行第19次之前,鎖對(duì)象的狀態(tài)都是輕量級(jí)鎖,但是到了第20次以后,鎖對(duì)象的狀態(tài)又變成了偏向鎖,而且偏向了線程t2,說(shuō)明觸發(fā)了偏向鎖的重新偏向。
在JVM中,以class(這里指BulkRevokeExample)為單位,為每個(gè)class維護(hù)了一個(gè)偏向鎖撤銷的計(jì)數(shù)器,當(dāng)這個(gè)class的對(duì)象發(fā)生偏向撤銷操作時(shí),計(jì)數(shù)器會(huì)進(jìn)行累加,當(dāng)累加的值達(dá)到重偏向的閾值時(shí),JVM會(huì)認(rèn)為這個(gè)class的偏向鎖有問(wèn)題,需要重新偏向。
2.7.2 輕量級(jí)鎖的實(shí)現(xiàn)原理
如果偏向鎖存在競(jìng)爭(zhēng)或者偏向鎖未開啟,那么當(dāng)線程訪問(wèn)synchronized(lock)同步代碼塊時(shí)就會(huì)采用輕量級(jí)鎖來(lái)?yè)屨兼i資源,獲得訪問(wèn)資格,輕量級(jí)鎖的加鎖原理如圖2-20所示。

圖2-20 輕量級(jí)鎖的加鎖原理
2.7.2.1 獲取輕量級(jí)鎖的實(shí)現(xiàn)流程
下面我們?cè)敿?xì)分析一下圖2-20中輕量級(jí)鎖的獲取實(shí)現(xiàn)流程。
第一步,在線程2進(jìn)入同步代碼塊后,JVM會(huì)給當(dāng)前線程分配一個(gè)Lock Record,也就是一個(gè)BasicObjectLock對(duì)象,在它的成員對(duì)象BasicLock中有一個(gè)成員屬性markOop _displaced_header,這個(gè)屬性專門用來(lái)保存鎖對(duì)象lock的原始Mark Word。
第二步,構(gòu)建一個(gè)無(wú)鎖狀態(tài)的Mark Word(其實(shí)就是lock鎖對(duì)象的Mark Word,但是鎖狀態(tài)是無(wú)鎖),把這個(gè)無(wú)鎖狀態(tài)的Mark Word設(shè)置到Lock Record中的_displaced_header字段中,如圖2-21所示。

圖2-21 Displaced Mark Word
第三步,通過(guò)CAS將lock鎖對(duì)象的Mark Word替換為指向Lock Record的指針,如果替換成功,就會(huì)得到如圖2-22所示的結(jié)構(gòu),表示輕量級(jí)鎖搶占成功,此時(shí)線程2可以執(zhí)行同步代碼塊。

圖2-22 CAS成功后的結(jié)構(gòu)
第四步,如果CAS失敗,則說(shuō)明當(dāng)前l(fā)ock鎖對(duì)象不是無(wú)鎖狀態(tài),會(huì)觸發(fā)鎖膨脹,升級(jí)到重量級(jí)鎖。
相對(duì)偏向鎖來(lái)說(shuō),輕量級(jí)鎖的原理比較簡(jiǎn)單,它只是通過(guò)CAS來(lái)修改鎖對(duì)象中指向Lock Record的指針。從功能層面來(lái)說(shuō),偏向鎖和輕量級(jí)鎖最大的不同是:
? 偏向鎖只能保證偏向同一個(gè)線程,只要有線程獲得過(guò)偏向鎖,那么當(dāng)其他線程去搶占鎖時(shí),只能通過(guò)輕量級(jí)鎖來(lái)實(shí)現(xiàn),除非觸發(fā)了重新偏向(如果獲得輕量級(jí)鎖的線程在后續(xù)的20次訪問(wèn)中,發(fā)現(xiàn)每次訪問(wèn)鎖的線程都是同一個(gè),則會(huì)觸發(fā)重新偏向,20次的定義屬性為:XX:BiasedLockingBulkRebiasThreshold =20)。
? 輕量級(jí)鎖可以靈活釋放,也就是說(shuō),如果線程1搶占了輕量級(jí)鎖,那么在鎖用完并釋放后,線程2可以繼續(xù)通過(guò)輕量級(jí)鎖來(lái)?yè)屨兼i資源。
可能有些讀者會(huì)有疑問(wèn),輕量級(jí)鎖中的CAS操作是先把lock鎖對(duì)象的Mark Word復(fù)制到當(dāng)前線程棧幀的Lock Record中,然后通過(guò)比較lock鎖對(duì)象的Mark Word和復(fù)制到Lock Record中的Mark Word是否相同來(lái)決定是否獲取鎖,那么不是會(huì)導(dǎo)致每個(gè)線程進(jìn)來(lái)都能CAS成功嗎?實(shí)際上并非如此,因?yàn)槊看卧贑AS之前都會(huì)判斷鎖的狀態(tài),只有在無(wú)鎖狀態(tài)時(shí)才會(huì)執(zhí)行CAS,所以并不會(huì)存在多個(gè)線程同時(shí)獲得鎖的問(wèn)題。
2.6.2.2 輕量級(jí)鎖的釋放
偏向鎖也有鎖釋放的邏輯,但是它只是釋放Lock Record,原本的偏向關(guān)系仍然存在,所以并不是真正意義上的鎖釋放。而輕量級(jí)鎖釋放之后,其他線程可以繼續(xù)使用輕量級(jí)鎖來(lái)?yè)屨兼i資源,具體的實(shí)現(xiàn)流程如下。
第一步,把Lock Record中_displaced_header存儲(chǔ)的lock鎖對(duì)象的Mark Word替換到lock鎖對(duì)象的Mark Word中,這個(gè)過(guò)程會(huì)采用CAS來(lái)完成。
第二步,如果CAS成功,則輕量級(jí)鎖釋放完成。
第三步,如果CAS失敗,說(shuō)明釋放鎖的時(shí)候發(fā)生了競(jìng)爭(zhēng),就會(huì)觸發(fā)鎖膨脹,完成鎖膨脹之后,再調(diào)用重量級(jí)鎖的釋放鎖方法,完成鎖的釋放過(guò)程。
為什么輕量級(jí)鎖在釋放鎖的時(shí)候會(huì)CAS失敗呢?讀者不妨想想,假設(shè)t1線程獲得了輕量級(jí)鎖,那么當(dāng)t2線程競(jìng)爭(zhēng)鎖的時(shí)候,由于無(wú)法獲得輕量級(jí)鎖,所以會(huì)觸發(fā)鎖膨脹,在鎖膨脹的邏輯中,會(huì)判斷如果當(dāng)前的鎖狀態(tài)是輕量級(jí)鎖,那么t2線程會(huì)修改鎖對(duì)象的Mark Word,將其設(shè)置為INFLATING狀態(tài)(這個(gè)過(guò)程是采用自旋鎖來(lái)實(shí)現(xiàn)的,當(dāng)存在多個(gè)線程觸發(fā)膨脹時(shí),只有一個(gè)線程去修改鎖對(duì)象的Mark Word)。
如果lock鎖對(duì)象的Mark Word在鎖膨脹的過(guò)程中發(fā)生了變化,那么持有輕量級(jí)鎖的線程通過(guò)CAS釋放時(shí)必然會(huì)失敗,因?yàn)榇鎯?chǔ)在當(dāng)前線程棧幀中的Lock Record的Mark Word和鎖對(duì)象lock的Mark Word已經(jīng)不相同了。
并且,持有輕量級(jí)鎖的線程t1在持有鎖期間,如果其他線程因?yàn)楦?jìng)爭(zhēng)不到鎖而升級(jí)到重量級(jí)鎖并且被阻塞,那么線程t1在釋放鎖時(shí),還需要喚醒處于重量級(jí)鎖阻塞狀態(tài)下的線程。
2.7.2.3 偏向鎖和輕量級(jí)鎖的對(duì)比
通過(guò)對(duì)偏向鎖和輕量級(jí)鎖的原理剖析,大家應(yīng)該對(duì)這兩種鎖的觸發(fā)場(chǎng)景的認(rèn)知更加深刻。
偏向鎖,就是在一段時(shí)間內(nèi)只由同一個(gè)線程來(lái)獲得和釋放鎖,加鎖的方式是把Thread Id保存到鎖對(duì)象的Mark Word中。
輕量級(jí)鎖,存在鎖交替競(jìng)爭(zhēng)的場(chǎng)景,在同一時(shí)刻不會(huì)有多個(gè)線程同時(shí)獲得鎖,它的實(shí)現(xiàn)方式是在每個(gè)線程的棧幀中分配一個(gè)BasicObjectLock對(duì)象(Lock Record),然后把鎖對(duì)象中的Mark Word拷貝到Lock Record中,最后把鎖對(duì)象的Mark Word的指針指向Lock Record。輕量級(jí)鎖之所以這樣設(shè)計(jì),是因?yàn)殒i對(duì)象在競(jìng)爭(zhēng)的過(guò)程中有可能會(huì)發(fā)生變化,但是每個(gè)線程的Lock Record的Mark Word不會(huì)受到影響。因此當(dāng)觸發(fā)鎖膨脹時(shí),能夠通過(guò)Lock Record和鎖對(duì)象的Mark Word進(jìn)行比較來(lái)判定在持有輕量級(jí)鎖的過(guò)程中,鎖對(duì)象是否被其他線程搶占過(guò),如果有,則需要在輕量級(jí)鎖釋放鎖的過(guò)程中喚醒被阻塞的其他線程。
2.7.3 重量級(jí)鎖的實(shí)現(xiàn)原理
如果線程在運(yùn)行synchronized(lock)同步代碼塊時(shí),發(fā)現(xiàn)鎖狀態(tài)是輕量級(jí)鎖并且有其他線程搶占了鎖資源,那么該線程就會(huì)觸發(fā)鎖膨脹升級(jí)到重量級(jí)鎖。因此,重量級(jí)鎖是在存在線程競(jìng)爭(zhēng)的場(chǎng)景中使用的鎖類型。重量級(jí)鎖的實(shí)現(xiàn)流程如圖2-23所示。

圖2-23 重量級(jí)鎖的實(shí)現(xiàn)流程
在獲取重量級(jí)鎖之前,會(huì)先實(shí)現(xiàn)鎖膨脹,在鎖膨脹的方法中首先創(chuàng)建一個(gè)ObjectMonitor對(duì)象,然后把ObjectMonitor對(duì)象的指針保存到鎖對(duì)象的Mark Word中,鎖膨脹分為四種情況,分別如下。
? 當(dāng)前已經(jīng)是重量級(jí)鎖的狀態(tài),不需要再膨脹,直接從鎖對(duì)象的Mark Word中獲取ObjectMonitor對(duì)象的指針?lè)祷丶纯伞?/p>
? 如果有其他線程正在進(jìn)行鎖膨脹,那么通過(guò)自旋的方式不斷重試直到其他線程完成鎖膨脹(其實(shí)就是創(chuàng)建一個(gè)ObjectMonitor對(duì)象)。
? 如果當(dāng)前有其他線程獲得了輕量級(jí)鎖,那么當(dāng)前線程會(huì)完成鎖的膨脹。
? 如果當(dāng)前是無(wú)鎖狀態(tài),也就是說(shuō)之前獲得鎖資源的線程正好釋放了鎖,那么當(dāng)前線程需完成鎖膨脹。
以上這四種情況都是在自旋的方式下完成的,避免了線程競(jìng)爭(zhēng)導(dǎo)致CAS失敗的問(wèn)題。
在鎖膨脹完成之后,鎖對(duì)象及ObjectMonitor的引用關(guān)系如圖2-24所示,lock鎖對(duì)象的Mark Word會(huì)保存指向ObjectMonitor的指針,重量級(jí)鎖的競(jìng)爭(zhēng)都是在ObjectMonitor中完成的。在ObjectMonitor中有一些比較重要的字段,解釋如下。
? _owner,保存當(dāng)前持有鎖的線程。
? _object,保存鎖對(duì)象的指針。
? _cxq,存儲(chǔ)沒(méi)有獲得鎖的線程的隊(duì)列,它是一個(gè)鏈表結(jié)構(gòu)。
? _WaitSet,當(dāng)調(diào)用Object.wait()方法阻塞時(shí),被阻塞的線程會(huì)保存到該隊(duì)列中。
? _recursions,記錄重入次數(shù)。

圖2-24 重量級(jí)鎖的引用關(guān)系
鎖膨脹完成之后,就開始在重量級(jí)鎖中實(shí)現(xiàn)鎖的競(jìng)爭(zhēng),下面分別從重量級(jí)鎖的獲取和釋放兩個(gè)環(huán)節(jié)進(jìn)行說(shuō)明。
2.7.3.1 重量級(jí)鎖的獲取流程
重量級(jí)鎖的實(shí)現(xiàn)是在ObjectMonitor中完成的,所以鎖膨脹的意義就是構(gòu)建一個(gè)ObjectMonitor,繼續(xù)關(guān)注圖2-24中ObjectMonitor的實(shí)現(xiàn)部分,在ObjectMonitor中鎖的實(shí)現(xiàn)過(guò)程如下:
首先,判斷當(dāng)前線程是否是重入,如果是則增加重入次數(shù)。
然后,通過(guò)自旋鎖來(lái)實(shí)現(xiàn)鎖的搶占(這個(gè)自旋鎖就是前面我們提到的自適應(yīng)自旋),這里使用CAS機(jī)制來(lái)判斷ObjectMonitor中的_owner字段是否為空,如果為空就表示重量級(jí)鎖已釋放,當(dāng)前線程可以獲得鎖,否則就進(jìn)行自適應(yīng)自旋重試。
最后,如果通過(guò)自旋鎖競(jìng)爭(zhēng)鎖失敗,則會(huì)把當(dāng)前線程構(gòu)建成一個(gè)ObjectWaiter節(jié)點(diǎn),插入_cxq隊(duì)列的隊(duì)首,再使用park方法阻塞當(dāng)前線程。
很多參考資料上描述的自旋操作是在輕量級(jí)鎖內(nèi)完成的,但是筆者在Hotspot 1.8的源碼中發(fā)現(xiàn),輕量級(jí)鎖中并沒(méi)有使用自旋操作。
2.7.3.2 重量級(jí)鎖的釋放原理
鎖的釋放是在synchronized同步代碼塊結(jié)束后觸發(fā)的,釋放的邏輯比較簡(jiǎn)單。
? 把ObjectMonitor中持有鎖的對(duì)象_owner置為null。
? 從_cxq隊(duì)列中喚醒一個(gè)處于鎖阻塞的線程。
? 被喚醒的線程會(huì)重新競(jìng)爭(zhēng)重量級(jí)鎖,需要注意的是,synchronized是非公平鎖,因此被喚醒后不一定能夠搶占到鎖,如果沒(méi)搶到,則繼續(xù)等待。
2.7.3.3 簡(jiǎn)述內(nèi)核態(tài)和用戶態(tài)
在重量級(jí)鎖中,線程的阻塞和喚醒是通過(guò)park()方法和unpark()方法來(lái)完成的,這是兩個(gè)與平臺(tái)相關(guān)的方法,對(duì)不同的操作系統(tǒng)有不同的實(shí)現(xiàn),比如在os_linux.cpp中,park()方法的實(shí)現(xiàn)代碼如下。


可以看到,park()方法實(shí)際上用到了3個(gè)方法。
? pthread_mutex_lock()方法,鎖定_mutext指向的互斥鎖。如果該互斥鎖已經(jīng)被另外一個(gè)線程鎖定和擁有,則當(dāng)前調(diào)用該方法的線程會(huì)阻塞,直到互斥鎖變?yōu)榭捎谩?/p>
? pthread_cond_wait()方法,條件等待,類似于Java中的Object.wait,與之配對(duì)的另外一個(gè)喚醒方法是pthread_cond_signal()。
? pthread_mutex_unlock()方法,釋放指定_mutex引用的互斥鎖對(duì)象。
在Linux中,系統(tǒng)的阻塞和喚醒是基于系統(tǒng)調(diào)用sys_futex來(lái)實(shí)現(xiàn)的,而系統(tǒng)調(diào)用是在內(nèi)核態(tài)運(yùn)行的,所以系統(tǒng)需要從用戶態(tài)切換到內(nèi)核態(tài)。在切換之前,首先要保存用戶態(tài)的狀態(tài),包括寄存器、程序指令等;然后執(zhí)行內(nèi)核態(tài)的系統(tǒng)指令調(diào)用;最后恢復(fù)到用戶態(tài)來(lái)執(zhí)行。這個(gè)過(guò)程會(huì)產(chǎn)生性能損耗,筆者在第1章中做了詳細(xì)的說(shuō)明。
用戶態(tài)(用戶空間)和內(nèi)核態(tài)(內(nèi)核空間)表示的是操作系統(tǒng)中的不同執(zhí)行權(quán)限,兩者最大的區(qū)別在于,運(yùn)行在用戶空間中的進(jìn)程不能直接訪問(wèn)操作系統(tǒng)內(nèi)核的指令和程序,而運(yùn)行在內(nèi)核空間的程序可以直接訪問(wèn)系統(tǒng)內(nèi)核的數(shù)據(jù)結(jié)構(gòu)和程序。操作系統(tǒng)之所以要做權(quán)限劃分,是為了避免用戶在進(jìn)程中直接操作一些存在潛在危險(xiǎn)的系統(tǒng)指令,從而影響其他進(jìn)程或者操作系統(tǒng)的穩(wěn)定性。
park()方法需要通過(guò)系統(tǒng)調(diào)用來(lái)完成,而系統(tǒng)調(diào)用只能在內(nèi)核空間實(shí)現(xiàn),因此就會(huì)導(dǎo)致用戶態(tài)到內(nèi)核態(tài)的切換。
- Dynamics 365 for Finance and Operations Development Cookbook(Fourth Edition)
- 兩周自制腳本語(yǔ)言
- Java Web基礎(chǔ)與實(shí)例教程(第2版·微課版)
- Python計(jì)算機(jī)視覺(jué)編程
- C語(yǔ)言程序設(shè)計(jì)教程
- C語(yǔ)言程序設(shè)計(jì)實(shí)驗(yàn)指導(dǎo) (第2版)
- 微服務(wù)從小白到專家:Spring Cloud和Kubernetes實(shí)戰(zhàn)
- IBM Cognos Business Intelligence 10.1 Dashboarding cookbook
- D3.js By Example
- ASP.NET程序開發(fā)范例寶典
- Building Wireless Sensor Networks Using Arduino
- Unity 2018 Augmented Reality Projects
- OpenCV 3計(jì)算機(jī)視覺(jué):Python語(yǔ)言實(shí)現(xiàn)(原書第2版)
- 貫通Tomcat開發(fā)
- iOS開發(fā)項(xiàng)目化入門教程