鎖到底是一種怎樣的存在?
鎖到底是一種怎樣的存在?
隨著業(yè)務(wù)的發(fā)展與用戶量的增加,高并發(fā)問題往往成為程序員不得不面對(duì)與處理的一個(gè)很棘手的問題,而并發(fā)編程又是編程領(lǐng)域相對(duì)高級(jí)與晦澀的知識(shí),想要學(xué)好并發(fā)相關(guān)的知識(shí),寫出好的并發(fā)程序不是那么容易的。
對(duì)于寫Java的程序員說,在這一點(diǎn)上可能要相對(duì)幸福一些,因?yàn)镴ava中存在大量的封裝好的同步原語以及大師編寫的同步工具類,使得編寫正確且高效的并發(fā)程序的門檻降低了許多。
這種高度的封裝抽象雖然簡(jiǎn)化了程序的書寫,卻對(duì)我們了解其內(nèi)部實(shí)現(xiàn)機(jī)制產(chǎn)生了一定的阻礙,現(xiàn)在就讓我們從現(xiàn)實(shí)世界中的鎖的角度進(jìn)行類比,看看程序世界中的鎖到底是一種怎樣的存在?
程序世界中的鎖
如果有人問你:"如何確保房屋不被陌生人進(jìn)入"?我想你可能很容易想到:“上鎖就可以了嘛!”。而如果有人問你:"如何處理多個(gè)線程的并發(fā)問題"?我想你可能脫口而出:"加鎖就可以了嘛!"。
類似的場(chǎng)景在現(xiàn)實(shí)世界中很容易理解,但是在程序世界中,這幾個(gè)字卻充滿了疑惑。我們見過現(xiàn)實(shí)世界中各種各樣的鎖,那Java中的鎖長(zhǎng)什么樣子?
我們現(xiàn)實(shí)世界中通常需要鑰匙打開鎖進(jìn)入房屋,那打開程序世界中的鎖的那把鑰匙是什么?現(xiàn)實(shí)中鎖通常位于門上或者櫥柜上或者其他位置,那程序世界中的鎖存在于哪里呢?現(xiàn)實(shí)世界中上鎖開鎖的通常是我們?nèi)?,那程序世界中加鎖解鎖的又是誰呢?
帶著這些疑問,我們想要深入的了解一下Java中鎖到底是一種怎樣的存在?從哪里開始了解呢,我想鎖在程序中首先是被用來使用的,那就先從鎖的使用開始偵查吧!
鎖的使用
提到 Java中的鎖,通??梢苑譃閮深悾活愂荍VM級(jí)別提供的并發(fā)同步原語Synchronized, 另一類就是 Java API級(jí)別的Lock接口的那些若干實(shí)現(xiàn)類。
Java API級(jí)別的鎖比如Reentrantlock和ReentrantReadWriteLock等這些存在很詳細(xì)的源碼,大家可以去看看他們是怎么實(shí)現(xiàn)的,也許可以尋找到上面的答案,這里我們看一下Synchronized。
先來看下面這段代碼:
- public class LockTest
- {
- Object obj=new Object();
- public static synchronized void testMethod1()
- {
- //同步代碼。
- }
- public synchronized void testMethod2()
- {
- //同步代碼
- }
- public void testMethod3()
- {
- synchronized (obj)
- {
- //同步代碼
- }
- }
- }
很多并發(fā)編程書籍對(duì)于Synchronized的用法都做了如下總結(jié):
- Synchronized修飾靜態(tài)方法的時(shí)候(對(duì)應(yīng)testMethod1),鎖的是當(dāng)前類的class對(duì)象,對(duì)應(yīng)到這里就是LockTest.class_對(duì)象_。
- Synchronized修飾實(shí)例方法的時(shí)候(對(duì)應(yīng)testMethod2),鎖的是當(dāng)前類實(shí)例的對(duì)象,對(duì)應(yīng)到這里就是LocKTest中的this引用_對(duì)象_。
- Synchronized修飾同步代碼塊的時(shí)候(對(duì)應(yīng)testMethod3),鎖的是同步代碼塊括號(hào)里的對(duì)象實(shí)例,對(duì)應(yīng)到這里就是obj_對(duì)象_。
從這里我們可以看到,Synchronized的使用都要依賴特定的對(duì)象,從這里可以發(fā)現(xiàn)鎖與對(duì)象存在某種關(guān)聯(lián)。那么我們下一步看看對(duì)象中到底有什么關(guān)于鎖的蛛絲馬跡。
對(duì)象的組成
Java中一切皆對(duì)象,就好比你的對(duì)象有長(zhǎng)長(zhǎng)的頭發(fā),大大的眼睛(或許一切只是想象)... Java中的對(duì)象由三部分組成。分別是對(duì)象頭、實(shí)例數(shù)據(jù)、對(duì)齊填充。
實(shí)例數(shù)據(jù)很好理解,就是我們?cè)陬愔卸x的那些字段數(shù)據(jù)所占用的空間。而對(duì)齊填充呢是因?yàn)镴ava特定的虛擬機(jī)要求對(duì)象的大小必須是8字節(jié)的整數(shù)倍,如果一個(gè)對(duì)象鎖占用的存儲(chǔ)空間最后會(huì)有一個(gè)不夠8字節(jié)的碎片,那么要把他填充到8字節(jié)??雌饋礞i與這兩個(gè)區(qū)域都不會(huì)有太大的關(guān)系,那么鎖應(yīng)該與對(duì)象頭存在某種關(guān)系,如下圖:
對(duì)象組成.png
下面來看一下對(duì)象頭中的內(nèi)容:
我們以32位虛擬機(jī)為例(64位的類比即可),Mark Word只有四個(gè)字節(jié),而且還要存放HashCode等信息,難道鎖就完全存在于這四個(gè)字節(jié)之內(nèi)就可以實(shí)現(xiàn)嘛?這句話在Jdk1.6之前是完全不對(duì)的,在Jdk1.6之后在一部分情況下是對(duì)的。
為什么這么說呢?
這是因?yàn)镴ava中的線程是與本地的操作系統(tǒng)線程一一對(duì)應(yīng)的,而操作系統(tǒng)為了保護(hù)系統(tǒng)內(nèi)部的安全,防止一些內(nèi)部指令等的隨意調(diào)用,保證內(nèi)核的安全,將系統(tǒng)空間分為了用戶態(tài)與內(nèi)核態(tài),我們平時(shí)所運(yùn)行的線程只是運(yùn)行在用戶態(tài),當(dāng)我們需要調(diào)用操作系統(tǒng)服務(wù)(這里被稱為系統(tǒng)調(diào)用),比如read,writer等操作時(shí),是沒有辦法在用戶態(tài)直接發(fā)起調(diào)用的,這個(gè)時(shí)候就需要進(jìn)行用戶態(tài)與內(nèi)核態(tài)的切換。
而Synchronized早期被稱為重量級(jí)鎖的原因是因?yàn)槭褂肧ynchronized所進(jìn)行的加鎖與解鎖都要進(jìn)行用戶態(tài)與內(nèi)核態(tài)的切換,所以早期的Synchronized是重量級(jí)鎖,需要實(shí)現(xiàn)線程的阻塞與喚醒,阻塞隊(duì)列與條件隊(duì)列的出隊(duì)與入隊(duì)等等,這些我們后面再說,顯然是不可能存放在這四個(gè)字節(jié)之內(nèi)的。但是Jdk1.6時(shí)對(duì)Synchronized進(jìn)行了一系列優(yōu)化,其中就包括了鎖升級(jí),使得這句話變得部分對(duì)了。
鎖升級(jí)的過程
之所以說前面那句話在部分情況下是正確的,是因?yàn)樵贘dk1.6時(shí),虛擬機(jī)團(tuán)隊(duì)對(duì)Synchronized進(jìn)行了一系列的優(yōu)化,具體我們就不討論了,很多的并發(fā)編程書籍中都有詳細(xì)的記錄。而這里我們要說的就是其中的一項(xiàng)重要的優(yōu)化——鎖升級(jí)。
Java中Synchronized的鎖升級(jí)過程如下:無鎖——>偏向鎖——>輕量級(jí)鎖——>重量級(jí)互斥鎖。
也就是說除非存在很嚴(yán)重的多線程之間的鎖競(jìng)爭(zhēng),否則Synchronized不會(huì)使用Jdk1.6之前那么重的互斥鎖了。
我們知道現(xiàn)實(shí)世界中是由我們?nèi)藖碡?fù)責(zé)進(jìn)行上鎖和開鎖的,那么程序世界中其實(shí)是由線程來扮演人的角色來進(jìn)行加鎖解鎖的。
偏向鎖
剛開始的時(shí)候,處于無鎖狀態(tài),我們可以理解為寶屋的門沒鎖著,這時(shí)第一個(gè)線程運(yùn)行到了同步代碼區(qū)域(第一個(gè)人走到了門前),加上了一個(gè)偏向鎖,這個(gè)時(shí)候鎖是一種什么形態(tài)呢?這個(gè)時(shí)候其實(shí)是類似一種人臉識(shí)別鎖的形態(tài),第一個(gè)進(jìn)入同步代碼塊的線程自身作為鑰匙,將能夠唯一標(biāo)識(shí)一個(gè)線程的線程ID保存到了Mark Word中。
這個(gè)時(shí)候的Mark Word中的內(nèi)容如下:
偏向鎖.jpg
這里的四個(gè)字節(jié)的23位用來存儲(chǔ)第一個(gè)獲取偏向鎖的線程的線程ID,2位的Epoch代表偏向鎖的有效性,4位對(duì)象分代年齡,1位是否是偏向鎖(1為是),2位鎖標(biāo)志位(01是偏向鎖)。
當(dāng)?shù)谝粋€(gè)線程運(yùn)行到同步代碼塊的時(shí)候,會(huì)去檢查Synchronized鎖使用的那個(gè)對(duì)象的對(duì)象頭,如果上面所談的Synchronized所使用的三種對(duì)象其中之一的對(duì)象頭的線程ID這個(gè)地方為空的話,并且偏向鎖是有效的,說明當(dāng)前還是處于無鎖的狀態(tài)(也就是寶屋還沒有上鎖),那么這個(gè)時(shí)候第一個(gè)線程就會(huì)使用CAS的方式將自己的線程ID替換到對(duì)象頭Mark Word的線程ID,如果替換成功說明該線程獲取到了偏向鎖,那么線程就可以安全的執(zhí)行同步代碼了,以后如果線程再次進(jìn)入同步代碼的時(shí)候,在此期間如果其他線程沒有獲取偏向鎖,只需要簡(jiǎn)單的對(duì)比一下自己的線程ID與Mark Word中的線程ID是否一致,如果一致就可以直接進(jìn)入同步代碼區(qū)域,這樣性能損耗就小多了。
偏向鎖是基于這樣的一個(gè)事實(shí),HotSpot的研發(fā)團(tuán)隊(duì)曾經(jīng)做個(gè)一個(gè)研究并表明,通常情況下鎖并不會(huì)發(fā)生競(jìng)爭(zhēng),并且總是由同一個(gè)線程多次的獲取鎖,在這種情況下引入偏向鎖可以說好處大大的了!
相反如果這種情況不是很常見的話,也就是說鎖的競(jìng)爭(zhēng)很嚴(yán)重,或者通常情況下鎖是由多個(gè)線程輪流獲取,這樣子偏向鎖就沒什么用處了。
輕量級(jí)鎖
從這里我們可以看出,當(dāng)鎖開始時(shí)是偏向鎖的時(shí)候是以一種怎樣的形態(tài)存在,前面我們也說了偏向鎖是在不存在多個(gè)線程競(jìng)爭(zhēng)鎖的情況下存在的,然而高并發(fā)環(huán)境下競(jìng)爭(zhēng)鎖是不可避免的,此時(shí)Synchronized便開啟了他的晉升之路。
當(dāng)存在多個(gè)線程競(jìng)爭(zhēng)鎖的時(shí)候,這時(shí)候簡(jiǎn)單的偏向鎖就不是那么安全了,鎖不住了,這時(shí)就要換鎖,升級(jí)成一種更為安全的鎖。此時(shí)的鎖升級(jí)過程大概可以分為兩步:(1)偏向鎖的撤銷(2)輕量級(jí)鎖的升級(jí)。
首先偏向鎖如何撤銷呢,我們說偏向鎖的鎖其實(shí)就是Mark Work中的線程ID,這個(gè)時(shí)候只要更改Mark Word自然就相當(dāng)于撤銷了偏向鎖,那么問題是偏向鎖用線程ID表示,輕量級(jí)鎖該用什么表示呢?答案是Lock Record(棧楨中的鎖記錄)。
這里我來解釋一下:
我們知道JVM內(nèi)存結(jié)構(gòu)可以分為(1)堆(2)虛擬機(jī)棧(3)本地方法棧(4)程序計(jì)數(shù)器(5)方法區(qū)(6)直接內(nèi)存。這其中程序計(jì)數(shù)器和虛擬機(jī)棧是線程私有的啊,每個(gè)線程都擁有自己獨(dú)立的??臻g,看起來存放在棧中可以很好的區(qū)分開是哪個(gè)線程獲取到了鎖,事實(shí)上,JVM也確實(shí)是這么做的。
首先,JVM會(huì)在當(dāng)前的棧中開辟一塊內(nèi)存,這塊內(nèi)存被稱為L(zhǎng)ock Record(鎖記錄),并把Mark Word中的內(nèi)容復(fù)制到Lock Record中(也就是說Lock Record中存放的是之前的Mark Work中的內(nèi)容,那為什么要存之前的內(nèi)容呢?
很簡(jiǎn)單,因?yàn)槲覀凂R上就要修改Mark Word的內(nèi)容了,修改之前當(dāng)然要保存一下,以便日后恢復(fù)啊),復(fù)制完了之后接下來就要開始修改Mark Word了,如何修改呢?當(dāng)然是用CAS的方式替換Mark Word了!此時(shí)Mark Word將變成以下內(nèi)容:
輕量級(jí)鎖.jpg
可以看到Mark Word中使用30位來記錄我們剛剛在棧楨中創(chuàng)建的Lock Record,鎖標(biāo)志位為00表示輕量級(jí)鎖,這樣就很容易知道是哪個(gè)線程獲取到了輕量級(jí)鎖啦。
輕量級(jí)鎖是基于這樣的一個(gè)事實(shí),當(dāng)存在兩個(gè)或以上的線程競(jìng)爭(zhēng)鎖的時(shí)候,絕大多數(shù)情況下,持有鎖的線程是會(huì)很快釋放鎖的,也就是當(dāng)鎖存在少量競(jìng)爭(zhēng)時(shí),通常情況下鎖被持有的時(shí)間很短,此時(shí)等待獲取鎖的線程可以不必進(jìn)行用戶態(tài)與內(nèi)核態(tài)的切換從而阻塞自己,而只要空循環(huán)(這個(gè)叫自旋)一會(huì)兒,期望在自旋的這段時(shí)候持有鎖的線程可以馬上釋放掉鎖。
很明顯輕量級(jí)鎖適用于鎖的競(jìng)爭(zhēng)并不激烈并且鎖被持有的時(shí)間很短的情況,相反如果鎖競(jìng)爭(zhēng)激烈或者線程獲取到鎖之后長(zhǎng)時(shí)間不釋放鎖,那么線程會(huì)白白的自旋(死循環(huán))而浪費(fèi)掉cpu資源。
重量級(jí)互斥鎖
當(dāng)想要進(jìn)入寶屋的人太多時(shí),輕量級(jí)也不行了,這個(gè)時(shí)候只能使用殺手锏了——重量級(jí)互斥鎖。這也是Synchronized在Jdk1.6之前的默認(rèn)實(shí)現(xiàn)。
當(dāng)鎖處于輕量級(jí)鎖的時(shí)候,線程需要自旋等待持有鎖的線程釋放鎖,然后去申請(qǐng)鎖,但是存在兩個(gè)問題:
1. 自旋的線程很多,也就是有很多線程都在等待當(dāng)前持有鎖的線程釋放鎖,由于鎖只能同一時(shí)刻被一個(gè)線程獲取(就Synchronized而言),這樣就導(dǎo)致大量的線程獲取鎖失敗,總不能一直的自旋下去吧?
2. 持有鎖的線程長(zhǎng)時(shí)間不釋放鎖,導(dǎo)致在外面等待獲取鎖的線程長(zhǎng)時(shí)間自旋仍然獲取不到鎖,總不能一直自旋下去吧?
上述兩種情況下分別來看,等待獲取鎖的線程就很難受了,如果兩種情況同時(shí)滿足(鎖競(jìng)爭(zhēng)激烈同時(shí)持有鎖的線程長(zhǎng)時(shí)間不釋放鎖),那就更難受了。于是JVM設(shè)定了一個(gè)自旋次數(shù)的限制,如果線程自旋了一定的次數(shù)之后仍然沒有獲取到鎖,那么可以視為鎖競(jìng)爭(zhēng)比較激烈的情況了,這個(gè)時(shí)候線程請(qǐng)求撤銷輕量級(jí)鎖,晉升為重量級(jí)的互斥鎖。
在輕量級(jí)鎖的時(shí)候,鎖是以Lock Record的形式存在的,那么到了重量級(jí)鎖的時(shí)候,該以什么形式存在呢?
重量級(jí)鎖的復(fù)雜度是最高的,由于持有鎖的線程在釋放鎖時(shí)候需要喚醒阻塞等待的線程,線程獲取不到鎖的時(shí)候需要進(jìn)入某一個(gè)阻塞區(qū)域統(tǒng)一阻塞等待,同時(shí)我們知道還有wait,notify條件的等待與喚醒需要處理,所以重量級(jí)鎖的實(shí)現(xiàn)需要一個(gè)額外的大殺器——Monitor。
在《Java并發(fā)編程的藝術(shù)》一書中有著這樣的描述:
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ì)說明。但是,方法的同步同樣可以使用這兩個(gè)指令來實(shí)現(xiàn)。
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ì)象的鎖。
我們以HotSpot虛擬機(jī)為例,其是用C++實(shí)現(xiàn)的,C++也是一門面向?qū)ο蟮恼Z言,因此,虛擬機(jī)設(shè)計(jì)團(tuán)隊(duì)這一次選擇以對(duì)象的形態(tài)表示鎖,同時(shí)C++也支持多態(tài),這里的Monitor其實(shí)是一種抽象,虛擬機(jī)中對(duì)于Monitor的實(shí)現(xiàn)使用ObjectMonitor實(shí)現(xiàn),關(guān)于Monitor與ObjectMonitor的關(guān)系可以類比Java中Map與HashMap的關(guān)系。
我們看一下ObjectMonitor的真容:
- ObjectMonitor()
- {
- _header = NULL;
- _count = 0;//用來記錄該線程獲取鎖的次數(shù)
- _waiters = 0,
- _recursions = 0;//鎖的重入次數(shù)
- _object = NULL;
- _owner = NULL;//指向持有ObjectMonitor的線程
- _WaitSet = NULL;//存放處于Wait狀態(tài)的線程的集合
- _WaitSetLock = 0 ;
- _Responsible = NULL ;
- _succ = NULL ;
- _cxq = NULL ;
- FreeNext = NULL ;
- _EntryList = NULL ;//所以等待獲取鎖而被阻塞的線程的集合
- _SpinFreq = 0 ;
- _SpinClock = 0 ;
- OwnerIsThread = 0 ;
- }
這里強(qiáng)烈建議大家去看一下基于AQS(抽象隊(duì)列同步器)實(shí)現(xiàn)的ReentrantLock的實(shí)現(xiàn)源碼,因?yàn)镽eentrantLock內(nèi)部的同步器實(shí)現(xiàn)思路基本上就是Synchronized實(shí)現(xiàn)中的Monitor的縮影。
首先ObjectMonitor中需要有一個(gè)指針指向當(dāng)前獲取鎖的線程,就是上面的owner,當(dāng)某一個(gè)線程獲取鎖的時(shí)候,將調(diào)用ObjectMonitor.enter()方法進(jìn)入同步代碼塊,獲取到鎖之后,就將owner設(shè)置為指向當(dāng)前線程,當(dāng)其他的線程嘗試獲取鎖的時(shí)候,就找到ObjectMonitor中的owner看看是否是自己,如果是的話,recursions和count自增1,代表該線程再次的獲取到了鎖(Synchronized是可重入鎖,持有鎖的線程可以再次的獲取鎖),否則的話就應(yīng)該阻塞起來,那么這些阻塞的線程放在哪里呢?
統(tǒng)一的放在EntryList中即可。當(dāng)持有鎖的線程調(diào)用wait方法時(shí)(我們知道wait方法會(huì)使得線程放棄cpu,并釋放自己持有的鎖,然后阻塞掛起自己,直到其他的線程調(diào)用了notify或者notifyAll方法為止),那么線程應(yīng)該釋放掉鎖,把owner置為空,并喚醒EntryList中阻塞等待獲取鎖的線程,然后將自己掛起并進(jìn)入waitSet集合中等待,當(dāng)其他持有鎖的線程調(diào)用了notify或者或者notifyAll方法時(shí),會(huì)將WaitSet中的某一個(gè)線程(notify)或者全部線程(notifyAll)從WaitSet中移動(dòng)到EntryList中等待競(jìng)爭(zhēng)鎖,當(dāng)線程要釋放鎖的時(shí)候,就會(huì)調(diào)用ObjectMonitor.exit()方法退出同步代碼塊。結(jié)合《Java并發(fā)編程的藝術(shù)》中的描述,一切都很清晰了。
鎖升級(jí)為重量級(jí)鎖同樣需要兩個(gè)步驟:(1)輕量級(jí)鎖的撤銷(2)重量級(jí)鎖升級(jí)。
要撤銷輕量級(jí)鎖,當(dāng)然要把保存在棧楨中的Lock Record中存儲(chǔ)的內(nèi)容再寫回Mark Work中,然后將棧楨中的Lock Record清理掉。此后需要?jiǎng)?chuàng)建一個(gè)ObjectMonitor對(duì)象,并且將Mark Word中的內(nèi)容保存到ObjectMonitor中(便于撤銷鎖的時(shí)候恢復(fù)Mark Word,這里是保存在了ObjectMonitor中)。那么如何尋找到這個(gè)ObjectMonitor對(duì)象呢?哈哈沒錯(cuò)就是在Mark Word中記錄指向ObjectMonitor對(duì)象的指針即可。如何修改替換Mark Word中的內(nèi)容呢?當(dāng)然會(huì)CAS啦!
鎖在重量級(jí)互斥鎖的形態(tài)下Mark Word中的內(nèi)容如下:
重量級(jí)鎖.jpg
可以看到Mark Word中使用30位來保存指向ObjectMonitor的指針,鎖標(biāo)記位為10,表示重量級(jí)鎖。
重量級(jí)鎖基于這樣的一個(gè)事實(shí),當(dāng)鎖存在嚴(yán)重的競(jìng)爭(zhēng),或者鎖持有的時(shí)間通常很長(zhǎng)的時(shí)候,等待獲取鎖的線程應(yīng)該阻塞掛起自身,等待獲得鎖的線程釋放鎖的時(shí)候的喚醒,這樣避免白白的浪費(fèi)cpu資源。
鎖形態(tài)的變遷
現(xiàn)在我們可以回答文章開頭“ Java中的鎖長(zhǎng)什么樣子?”這個(gè)問題了,在不同的鎖狀態(tài)下,鎖表現(xiàn)出了不同的形態(tài)。
當(dāng)鎖以偏向鎖存在的時(shí)候,鎖就是Mark Word中的Thread ID,此時(shí)線程本身就是打開鎖的鑰匙,Mark Word中存了哪個(gè)線程的"身份證",哪個(gè)線程就獲得了鎖。
當(dāng)鎖以輕量級(jí)鎖存在的時(shí)候,鎖就是Mark Word中所指向棧楨中鎖記錄的Lock Record,此時(shí)的鑰匙就是地盤,是虛擬機(jī)棧,誰的棧中有Lock Record,誰就獲得了鎖。
當(dāng)鎖以重量級(jí)鎖存在的時(shí)候,鎖就是C++中對(duì)于Monitor的實(shí)現(xiàn)ObjectMonitor,此時(shí)的鑰匙就是ObjectMonitor中的owner。owner指向誰,誰就獲得了鎖。
之前的問題中,我們說32位的虛擬機(jī)Mark Word只有四個(gè)字節(jié),難道鎖就完全存在于這四個(gè)字節(jié)之內(nèi)就可以實(shí)現(xiàn)嘛?這句話在Jdk1.6之前是完全不對(duì)的,在Jdk1.6之后在一部分情況下是對(duì)的?,F(xiàn)在你是否對(duì)這句話有了更深刻的理解呢?
而現(xiàn)實(shí)世界中上鎖開鎖的是我們?nèi)祟?,通過前面的了解,程序世界中上鎖開鎖的又是誰呢?是的就是線程了。
現(xiàn)在再回頭看文章開頭的那些問題,就很容易給出答案了,原來一切真的就是從Synchronized使用的那個(gè)鎖對(duì)象開始的!
關(guān)于CAS
盡管經(jīng)歷了一系列優(yōu)化的Synchronized已經(jīng)比原來性能好了很多,但是業(yè)務(wù)越來越追求低延遲高響應(yīng)性,以樂觀并發(fā)控制為代表的CAS并發(fā)控制方式越來越受到青睞??梢钥吹紺AS在非阻塞式的原子替換上確實(shí)具有很好地應(yīng)用效果,有趣的是,通過前面的了解,Synchronized的升級(jí)過程中大量的使用到了CAS進(jìn)行Mark Word的非阻塞修改與替換,這在很多方面都值得我們學(xué)習(xí)。