日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

synchronized锁升级_synchronized详解以及锁的膨胀升级过程

發(fā)布時間:2025/3/15 编程问答 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 synchronized锁升级_synchronized详解以及锁的膨胀升级过程 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

點(diǎn)擊上方"碼之初"關(guān)注,···選擇"設(shè)為星標(biāo)"

與精品技術(shù)文章不期而遇

來源:www.cnblogs.com/cxiaocai/p/12189848.html

架構(gòu)之路遠(yuǎn)且艱辛,但是學(xué)習(xí)的腳步始終不能停止,今天聊一聊synchronized。

synchronized是jvm內(nèi)部的一把隱式鎖,一切的加鎖和解鎖過程是由jvm虛擬機(jī)來控制的,不需要我們認(rèn)為的干預(yù),我們大致從了解鎖,到synchronized的使用,到鎖的膨脹升級過程三個角度來說一下synchronized。

鎖的分類

java中我們聽到很多的鎖,什么顯示鎖,隱式鎖,公平鎖,重入鎖等等,下面我來總結(jié)一張圖來供大家學(xué)習(xí)使用。

這次我們主要來說我們的隱示鎖,就是我們的無鎖到重量級鎖。

synchronized的使用

我們先來看一段簡單的代碼

public?class?SynchronizedTest?{

????private?static?Object?object?=?new?Object();
????
????public?static?void?main(String[]?args)?{
????????synchronized?(object){
????????????System.out.println("只有我拿到鎖啦");
????????}
????}
}

就這樣synchronized就可以使用了,這樣是每次去拿全局對象的object去鎖住后續(xù)的代碼段。我們來看一下匯編指令碼

?public?static?void?main(java.lang.String[]);
????Code:
???????0:?getstatic?????#2??????????????????//?Field?object:Ljava/lang/Object;
???????3:?dup
???????4:?astore_1
???????5:?monitorenter
???????6:?getstatic?????#3??????????????????//?Field?java/lang/System.out:Ljava/io/PrintStream;
???????9:?ldc???????????#4??????????????????//?String?只有我拿到鎖啦
??????11:?invokevirtual?#5??????????????????//?Method?java/io/PrintStream.println:(Ljava/lang/String;)V
??????14:?aload_1
??????15:?monitorexit
??????16:?goto??????????24
??????19:?astore_2
??????20:?aload_1
??????21:?monitorexit
??????22:?aload_2
??????23:?athrow
??????24:?return
????Exception?table:
???????from????to??target?type
???????????6????16????19???any
??????????19????22????19???any

明顯看到了兩個很重要的方法monitorentermonitorexit兩個方法,也就是說我們的synchronized方法加鎖是基于monitorenter加鎖和monitorexit解鎖來操作的

我們得知是由monitorenter來控制加鎖和monitorexit解鎖的,我們完全可以這樣來操作。上次我們說過一個unsafe類。

public?class?SynchronizedTest?{

????private?static?Object?obj?=?new?Object();

????public?void?lockMethod(){
????????UnsafeInstance.reflectGetUnsafe().monitorEnter(obj);
????}
????
????public?void?unLockMethod(){
????????UnsafeInstance.reflectGetUnsafe().monitorExit(obj);
????}
}

就是我們上次說的unsafe那個類給我們提供了加鎖和解鎖的方法,這樣就是實(shí)現(xiàn)夸方法的加鎖和解鎖了,但是超級不建議這樣的使用,后面的AQS回去說別的方式。越過虛擬機(jī)直接操作底層的,我們一般是不建議這樣來做的。

我們還可以將synchronized鎖放置在方法上。例如

public?class?SynchronizedTest?{

????private?static?Object?object?=?new?Object();

????public?static?synchronized?void?lockMethod()?{
????????System.out.println("只有我拿到鎖啦");
????}
}

這樣加鎖是加在了this當(dāng)前類對象上的。如果不加static,鎖是加在類對象上的,需要注意我們用的spring的bean作用域。

并且我們的synchronized是一個可重入鎖,在jvm源碼中有一個數(shù)值來記錄加鎖和解鎖的次數(shù),所以我們是可以多次套用synchronized的

public?void?lockMethod(){
????synchronized(obj){
????????synchronized(obj){
????????????System.out.println("我沒報錯");
????????}
????}
}

synchronized到底鎖了什么

還是拿上個每次加鎖的時候會在對象頭內(nèi)記錄我們的加鎖信息,我們這里來說一下對象頭里面都放置了什么吧。

以32位JVM內(nèi)部存儲結(jié)構(gòu)為例

由此看出對象一直是有一個位置來記錄我們的鎖信息的。說到這我們就可以來看一下我們鎖的膨脹升級過程了。

鎖的膨脹升級

我們說過了對象頭的內(nèi)容,接下來可以說說我們的鎖內(nèi)部是如何升級上鎖的了。從無鎖到重量級鎖的一個升級過程,我們來邊畫圖,邊詳細(xì)看一下。

無鎖狀態(tài):

開始時應(yīng)該這樣的,線程A和線程B要去爭搶鎖對象,但還未開始爭搶,鎖對象的對象頭是無鎖的狀態(tài)也就是25bit位存的hashCode,4bit位存的對象的分代年齡,1bit位記錄是否為偏向鎖,2bit位記錄狀態(tài),優(yōu)先看最后2bit位,是01,所以說我們的對象可能無鎖或者偏向鎖狀態(tài)的,繼續(xù)前移一個位置,有1bit專門記錄是否為偏向鎖的,1代表是偏向鎖,0代表無鎖,剛剛開始的時候一定是一個無鎖的狀態(tài),這個不需要多做解釋,系統(tǒng)不同內(nèi)部bit位存的東西可能有略微差異,但關(guān)鍵信息是一致的。

偏向鎖:

這時線程開始占有鎖對象,比如線程A得到了鎖對象。

就會變成這樣的,線程A拿到鎖對象,將我們的偏向鎖標(biāo)志位改為1,并且將原有的hashCode的位置變?yōu)?3bit位存放線程A的線程ID(用CAS算法得到的線程A的ID),2bit位存epoch,偏向鎖是永遠(yuǎn)不會被釋放的。

接下來,線程B也開始運(yùn)行,線程B也希望得到這把鎖啊,于是線程B會檢查23bit位存的是不是自己的線程ID,因為被線程A已經(jīng)持有了,一定鎖的23bit位一定不是線程B的線程ID了

然后線程B也會不甘示弱啊,會嘗試修改一次23bit位的對象頭存儲,如果說這時恰好線程A釋放了鎖,可以修改成功,然后線程B就可以持有該偏向鎖了。如果修改失敗,開始升級鎖。自己無法修改,線程B只能找“大哥”了,線程B會通知虛擬機(jī)撤銷偏向鎖,然后虛擬機(jī)會撤銷偏向鎖,并告知線程A到達(dá)安全點(diǎn)進(jìn)行等待。線程A到達(dá)了安全點(diǎn),會再次判斷線程是否已經(jīng)退出了同步塊,如果退出了,將23bit位置空,這時鎖不需要升級,線程B可以直接進(jìn)行使用了,還是將23bit的null改為線程B的線程ID就可以了。

輕量級鎖:

如果線程B沒有拿到鎖,我們就會升級到輕量級鎖,首先會在線程A和線程B都開辟一塊LockRecord空間,然后把鎖對象復(fù)制一份到自己的LockRecord空間下,并且開辟一塊owner空間留作執(zhí)行鎖使用,并且鎖對象的前30bit位合并,等待線程A和線程B來修改指向自己的線程,假如線程A修改成功,則鎖對象頭的前30bit位會存線程A的LockRecord的內(nèi)存地址,并且線程A的owner也會存一份鎖對象的內(nèi)存地址,形成一個雙向指向的形式。而線程B修改失敗,則進(jìn)入一個自旋狀態(tài),就是持續(xù)來修改鎖對象。

重量級鎖:

如果說線程B多次自旋以后還是遲遲沒有拿到鎖,他會繼續(xù)上告,告知虛擬機(jī),我多次自旋還是沒有拿到鎖,這時我們的線程B會由用戶態(tài)切換到內(nèi)核態(tài),申請一個互斥量,并且將鎖對象的前30bit指向我們的互斥量地址,并且進(jìn)入睡眠狀態(tài),然后我們的線程A繼續(xù)運(yùn)行知道完成時,當(dāng)線程A想要釋放鎖資源時,發(fā)現(xiàn)原來鎖的前30bit位并不是指向自己了,這時線程A釋放鎖,并且去喚醒那些處于睡眠狀態(tài)的線程,鎖升級到重量級鎖。

逃逸分析

很簡單的一個問題,實(shí)例對象存在哪里?到底是堆還是棧?問題我先不回答,我們先看一段代碼。

public?class?Test?{

????public?static?void?main(String[]?args)?throws?InterruptedException?{
????????System.out.println("開始");
????????for?(int?i?=?0;?i?????????????createCar();
????????}
????????System.out.println("結(jié)束");
????????Thread.sleep(10000000);
????}


????private?static?void?createCar()?{
????????Car?car?=?new?Car();
????}
}

就是我們運(yùn)行一個創(chuàng)建對象的方法,一次性創(chuàng)建50萬個Car對象,然后我們讓我們的線程進(jìn)行深度的睡眠,兩個打印是為了知道我們的對象已經(jīng)開始創(chuàng)建了和已經(jīng)創(chuàng)建完成了。我們來運(yùn)行一下。

然后運(yùn)行jmap -histo命令來查看我們的線程

我們可以看到,car對象并沒有產(chǎn)生50萬個,別說會被GC掉對象,在運(yùn)行之前我已經(jīng)加了GC日志的參數(shù)-XX:+PrintGCDetails,控制臺沒有打印任何GC日志的。那么為什么會這樣呢?我們來看一下我們的代碼,由createCar代碼創(chuàng)建了car對象,但car對象并沒有被其它的方法或者線程去調(diào)用,虛擬機(jī)會認(rèn)為你這對象可能只是一個實(shí)例化,并沒有進(jìn)行使用,這時虛擬機(jī)會給予你一個優(yōu)化,就是對于可能沒有使用的對象進(jìn)行一次逃逸,也就是我們說到的逃逸分析。我們加入 -XX:-DoEscapeAnalysis參數(shù)再看一次。

這也就是關(guān)閉了我們的逃逸分析,虛擬機(jī)就會真的為我們創(chuàng)建了50萬個對象。也就是說開啟了逃逸分析有一部分對象只是創(chuàng)建了線程棧上,當(dāng)線程棧結(jié)束,對象也被銷毀,上面的問題也就有答案了,實(shí)例對象可能存在堆上,也可能存在棧上。

感謝大家的閱讀,不正確的地方,還希望大家來斧正,我們一起探討技術(shù)問題,覺得寫得好的,給個推薦,給個贊,點(diǎn)個關(guān)注吧,鞠躬,謝謝?。

好文!必須在看

總結(jié)

以上是生活随笔為你收集整理的synchronized锁升级_synchronized详解以及锁的膨胀升级过程的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。