多線程高并發(fā):synchronized鎖升級過程及其實(shí)現(xiàn)原理

問:為什么會(huì)有鎖升級的過程呢?

答:在java6以前synchronized鎖實(shí)現(xiàn)都是重量級鎖的形式,效率低下,為了提升效率進(jìn)行了優(yōu)化,所以出現(xiàn)了鎖升級的過程。

問:我們通常說synchronized鎖是重量級鎖,那么為什么叫他重量級鎖?

答:因?yàn)閟ynchronized執(zhí)行效率太低。在java1.6以前每次調(diào)用synchronized加鎖時(shí)都需要進(jìn)行系統(tǒng)調(diào)用,系統(tǒng)調(diào)用會(huì)涉及到用戶態(tài)和內(nèi)核態(tài)的切換,系統(tǒng)調(diào)用會(huì)經(jīng)過0x80中斷,經(jīng)過內(nèi)核調(diào)用后再返回用戶態(tài)。此過程比較復(fù)雜時(shí)間比較長所以通常叫synchronized為重量級鎖。

誤區(qū):其實(shí)鎖升級過程中涉及到的鎖偏向鎖,輕量級鎖都是synchronized鎖的具體實(shí)現(xiàn)所要經(jīng)歷的過程,他們并不是單獨(dú)的鎖。只是給他們這幾種鎖的狀態(tài)起了一個(gè)名字而已。

CAS

在介紹synchronized鎖升級過程之前,我們需要先了解cas的原理,為什么呢?因?yàn)閏as貫穿了整個(gè)synchronized鎖升級的過程。

CAS : compare and swap 或者 compare and exchange 比較交換。

當(dāng)我們需要對內(nèi)存中的數(shù)據(jù)進(jìn)行修改操作時(shí),為了避免多線程并發(fā)修改的情況,我們在對它進(jìn)行修改操作前,先讀取它原來的值E,然后進(jìn)行計(jì)算得出新的的值V,在修改前去比較當(dāng)前內(nèi)存中的值N是否和我之前讀到的E相同,如果相同,認(rèn)為其他線程沒有修改過內(nèi)存中的值,如果不同,說明被其他線程修改了,這時(shí),要繼續(xù)循環(huán)去獲取最新的值E,再進(jìn)行計(jì)算和比較,直到我們預(yù)期的值和當(dāng)前內(nèi)存中的值相等時(shí),再對數(shù)據(jù)執(zhí)行修改操作。

CAS具體流程如下下圖:

image.png

它是為了實(shí)現(xiàn)java中的原子操作而出現(xiàn)的。為了保證在比較完成后賦值這兩個(gè)操作的原子性,jvm內(nèi)部實(shí)現(xiàn)cas操作時(shí)通過LOCK CMPXCHG指令鎖cpu總線方式實(shí)現(xiàn)原子操作的。

對象頭

synchronized用的鎖是存在java對象頭里的。

32位java對象頭結(jié)構(gòu)如下表所示:


image.png

對于64位的java對象頭其余信息基本不變,只是中間有關(guān)于對象hashcode值和之后加鎖信息的位數(shù)加大以外,其他基本不變。
64位虛擬機(jī)系統(tǒng)下java對象頭在不同鎖狀態(tài)下的狀態(tài)變化如下表所示:

image.png

如上圖所示:其中最后兩位代表是否加鎖的標(biāo)志位。鎖標(biāo)志位如果是01的話需要根據(jù)前一位的是否為偏向鎖來判斷當(dāng)前的鎖狀態(tài),如果前一位為0則代表無鎖狀態(tài),如果為1則代表有偏向鎖。

后兩位:00代表輕量級鎖,10代表重量級鎖,11代表GC垃圾回收的標(biāo)記信息。

偏向鎖

偏向鎖產(chǎn)生的原因?

大多數(shù)情況下,鎖不僅不存在多線程競爭,而且總是由同一線程多次獲得,為了讓線程獲得鎖的代價(jià)更低而引入了偏向鎖。

獲取偏向鎖流程:

當(dāng)一個(gè)線程訪問同步塊時(shí),會(huì)先判斷鎖標(biāo)志位是否為01,如果是01,則判斷是否為偏向鎖,如果是,會(huì)先判斷當(dāng)前鎖對象頭中是否存儲(chǔ)了當(dāng)前的線程id,如果存儲(chǔ)了,則直接獲得鎖。如果對象頭中指向不是當(dāng)前線程id,則通過CAS嘗試將自己的線程id存儲(chǔ)進(jìn)當(dāng)前鎖對象的對象頭中來獲取偏向鎖。當(dāng)cas嘗試獲取偏向鎖成功后則繼續(xù)執(zhí)行同步代碼塊,否則等待安全點(diǎn)的到來撤銷原來線程的偏向鎖,撤銷時(shí)需要暫停原持有偏向鎖的線程,判斷線程是否活動(dòng)狀態(tài),如果已經(jīng)退出同步代碼塊則喚醒新的線程開始獲取偏向鎖,否則開始鎖競爭進(jìn)行鎖升級過程,升級為輕量級鎖。

偏向鎖獲取流程如下圖:

image.png

在高并發(fā)下可以關(guān)閉偏向鎖來提升性能,通過設(shè)置JVM參數(shù) -XX:-UseBiasedLocking=false。

輕量級鎖

當(dāng)出現(xiàn)鎖競爭時(shí),會(huì)升級為輕量級鎖。

在升級輕量級鎖之前,JVM會(huì)先在當(dāng)前線程的棧幀中創(chuàng)建用于存儲(chǔ)鎖記錄的空間即將對象頭中用來標(biāo)記鎖信息相關(guān)的內(nèi)容封裝成一個(gè)java對象放入當(dāng)前線程的棧幀中,這個(gè)對象稱為LockRcord,然后線程嘗試通過CAS將對象頭中mark word替換為指向鎖記錄(lockrecord)的指針。如果成功則當(dāng)前線程獲取鎖,如果失敗則使用自旋來獲取鎖。自旋其實(shí)就是不斷的循環(huán)進(jìn)行CAS操作直到能成功替換。所以輕量級鎖又叫自旋鎖。

下圖來源于網(wǎng)絡(luò)

棧上分配LockRecord: lockrecord中包含了對象的引用地址。

對象頭中markword替換鎖記錄指針成功之后如下圖:

image.png

替換成功之后將鎖標(biāo)志位改為00 表示獲取輕量級鎖成功。

lockrecord的作用:在這里實(shí)現(xiàn)了鎖重入,每當(dāng)同一個(gè)線程多次獲取同一個(gè)鎖時(shí),會(huì)在當(dāng)前棧幀中放入一個(gè)lockrecord,但是重入是放入的lockrecord關(guān)于鎖信息的內(nèi)容為null,代表鎖重入。當(dāng)輕量級解鎖時(shí),每解鎖一次則從棧幀中彈出一個(gè)lockrecord,直到為0.

輕量級鎖重入之后如下圖:

image.png

當(dāng)通過CAS自旋獲取輕量級鎖達(dá)到一定次數(shù)時(shí),JVM會(huì)發(fā)生鎖膨脹升級為重量級鎖。

原因:不斷的自旋在高并發(fā)的下會(huì)消耗大量的cpu資源,所以jvm為了節(jié)省cpu資源,進(jìn)行了鎖升級。將等待獲取鎖的線程都放入一個(gè)等待隊(duì)列中來節(jié)省cpu資源。

重量級鎖

在重量級鎖中將LockRecord對象替換為了monitor對象的實(shí)現(xiàn)。主要通過monitorenter和monitorexit兩個(gè)指令來實(shí)現(xiàn)。需要經(jīng)過系統(tǒng)調(diào)用,在并發(fā)低的情況下效率會(huì)低。

通過openJDK可以查看ObjectMonitor對象的結(jié)構(gòu):
http://hg.openjdk.java.net/jdk8/jdk8/hotspot/file/9758d9f36299/src/share/vm/runtime/objectMonitor.hpp

ObjectMonitor() {
    _header       = NULL;
    _count        = 0;
    _waiters      = 0,
    _recursions   = 0;
    _object       = NULL;
    _owner        = NULL; //擁有當(dāng)前對象的線程
    _WaitSet      = NULL; //阻塞隊(duì)列
    _WaitSetLock  = 0 ;
    _Responsible  = NULL ;
    _succ         = NULL ;
    _cxq          = NULL ;
    FreeNext      = NULL ;
    _EntryList    = NULL ; //有資格成為候選資源的線程隊(duì)列
    _SpinFreq     = 0 ;
    _SpinClock    = 0 ;
    OwnerIsThread = 0 ;
    _previous_owner_tid = 0;
  }

使用monitor加鎖如下圖:

image.png

重量級鎖在進(jìn)行鎖重入的時(shí)候每獲取到鎖一次會(huì)對monitor對象中的計(jì)數(shù)器+1,等鎖退出時(shí)則會(huì)相應(yīng)的-1,直到減到0為止,鎖完全退出。

幾種鎖狀態(tài)優(yōu)缺點(diǎn)對比

https://p26.toutiaoimg.com/origin/pgc-image/a983c3bbdf814797add6d3a7515dfe10?from=pc

總結(jié)

綜上,我們發(fā)現(xiàn)偏向鎖,輕量級鎖(又稱自旋鎖或無鎖),重量級鎖都是synchronized鎖鎖實(shí)現(xiàn)中鎖經(jīng)歷的幾種不同的狀態(tài)。

三種鎖狀態(tài)的場景總結(jié):

  • 只有一個(gè)線程進(jìn)入臨界區(qū) -------偏向鎖
  • 多個(gè)線程交替進(jìn)入臨界區(qū)--------輕量級鎖
  • 多個(gè)線程同時(shí)進(jìn)入臨界區(qū)-------重量級鎖

作者:little_color

原文鏈接:
https://blog.csdn.net/wangyy130/article/details/106495180

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

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

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