<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          java架構(gòu)之路(多線程)synchronized詳解以及鎖的膨脹升級(jí)過程

          共 3974字,需瀏覽 8分鐘

           ·

          2020-08-04 16:13

          來(lái)自:博客園(作者:小菜技術(shù))

          原文鏈接(底部鏈接可直達(dá)):

          https://www.cnblogs.com/cxiaocai/p/12189848.html


          前言

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

          鎖的分類

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

          這次博客我們主要來(lái)說(shuō)我們的隱示鎖,就是我們的無(wú)鎖到重量級(jí)鎖。

          synchronized的使用

          我們先來(lái)看一段簡(jiǎn)單的代碼

          public?class?SynchronizedTest?{

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

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

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

          ?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


          明顯看到了兩個(gè)很重要的方法monitorenter和monitorexit兩個(gè)方法,也就是說(shuō)我們的synchronized方法加鎖是基于monitorenter加鎖和monitorexit解鎖來(lái)操作的 

          我們得知是由monitorenter來(lái)控制加鎖和monitorexit解鎖的,我們完全可以這樣來(lái)操作。上次我們說(shuō)過一個(gè)unsafe類。

          public?class?SynchronizedTest?{

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

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

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


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


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


          public?class?SynchronizedTest?{

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

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


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


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


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

          synchronized到底鎖了什么

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

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

          由此看出對(duì)象一直是有一個(gè)位置來(lái)記錄我們的鎖信息的。說(shuō)到這我們就可以來(lái)看一下我們鎖的膨脹升級(jí)過程了。

          鎖的膨脹升級(jí)

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

          無(wú)鎖狀態(tài):

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

          偏向鎖:

          這時(shí)線程開始占有鎖對(duì)象,比如線程A得到了鎖對(duì)象。

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

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

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

          輕量級(jí)鎖:

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

          重量級(jí)鎖:

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

          逃逸分析

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

          public?class?Test?{

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


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


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

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

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

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

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


          推薦閱讀:


          喜歡我可以給我設(shè)為星標(biāo)哦

          好文章,我“在看”
          瀏覽 54
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  看黄色毛片 | 人人看人人爱人人搞 | 日韩欧美大香蕉 | 国产激情内射 | 男人的天堂欧美 |