真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

怎么理解synchronized與鎖的關(guān)系

這篇文章主要講解了“怎么理解synchronized與鎖的關(guān)系”,文中的講解內(nèi)容簡單清晰,易于學(xué)習(xí)與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“怎么理解synchronized與鎖的關(guān)系”吧!

網(wǎng)站建設(shè)哪家好,找成都創(chuàng)新互聯(lián)!專注于網(wǎng)頁設(shè)計(jì)、網(wǎng)站建設(shè)、微信開發(fā)、成都小程序開發(fā)、集團(tuán)企業(yè)網(wǎng)站建設(shè)等服務(wù)項(xiàng)目。為回饋新老客戶創(chuàng)新互聯(lián)還提供了彌渡免費(fèi)建站歡迎大家使用!

JVM 是如何實(shí)現(xiàn) synchronized 的?

我知道可以利用 synchronized  關(guān)鍵字來給程序進(jìn)行加鎖,但是它具體怎么實(shí)現(xiàn)的我不清楚呀,別急,咱們先來看個(gè) demo :

public class demo {     public void synchronizedDemo(Object lock){   synchronized(lock){    lock.hashCode();   }  } }

上面是我寫的一個(gè) demo ,然后進(jìn)入到 class 文件所在的目錄下,使用 javap -v demo.class  來看一下編譯的字節(jié)碼(在這里我截取了一部分):

public void synchronizedDemo(java.lang.Object);   descriptor: (Ljava/lang/Object;)V   flags: ACC_PUBLIC   Code:     stack=2, locals=4, args_size=2        0: aload_1        1: dup        2: astore_2        3: monitorenter        4: aload_1        5: invokevirtual #2                  // Method java/lang/Object.hashCode:()I        8: pop        9: aload_2       10: monitorexit       11: goto          19       14: astore_3       15: aload_2       16: monitorexit       17: aload_3       18: athrow       19: return     Exception table:        from    to  target type            4    11    14   any           14    17    14   any

應(yīng)該能夠看到當(dāng)程序聲明 synchronized 代碼塊時(shí),編譯成的字節(jié)碼會包含 monitorenter和 monitorexit  指令,這兩種指令會消耗操作數(shù)棧上的一個(gè)引用類型的元素(也就是 synchronized  關(guān)鍵字括號里面的引用),作為所要加鎖解鎖的鎖對象。如果看的比較仔細(xì)的話,上面有一個(gè) monitorenter 指令和兩個(gè) monitorexit 指令,這是  Java 虛擬機(jī)為了確保獲得的鎖不管是在正常執(zhí)行路徑,還是在異常執(zhí)行路徑上都能夠解鎖。

  • 關(guān)于 monitorenter 和 monitorexit ,可以理解為每個(gè)鎖對象擁有一個(gè)鎖計(jì)數(shù)器和一個(gè)指向持有該鎖的線程指針:

  • 當(dāng)程序執(zhí)行 monitorenter 時(shí),如果目標(biāo)鎖對象的計(jì)數(shù)器為 0 ,說明這個(gè)時(shí)候它沒有被其他線程所占有,此時(shí)如果有線程來請求使用, Java  虛擬機(jī)就會分配給該線程,并且把計(jì)數(shù)器的值加 1

  • 目標(biāo)鎖對象計(jì)數(shù)器不為 0 時(shí),如果鎖對象持有的線程是當(dāng)前線程, Java 虛擬機(jī)可以將其計(jì)數(shù)器加 1  ,如果不是呢?那很抱歉,就只能等待,等待持有線程釋放掉

當(dāng)執(zhí)行 monitorexit 時(shí), Java 虛擬機(jī)就將鎖對象的計(jì)數(shù)器減 1 ,當(dāng)計(jì)數(shù)器減到 0  時(shí),說明這個(gè)鎖就被釋放掉了,此時(shí)如果有其他線程來請求,就可以請求成功

為什么采用這種方式呢?是為了允許同一個(gè)線程重復(fù)獲取同一把鎖。比如,一個(gè) Java 類中擁有好多個(gè) synchronized  方法,那這些方法之間的相互調(diào)用,不管是直接的還是間接的,都會涉及到對同一把鎖的重復(fù)加鎖操作。這樣去設(shè)計(jì)的話,就可以避免這種情況。

怎么理解synchronized與鎖的關(guān)系

在 Java 多線程中,所有的鎖都是基于對象的。也就是說, Java 中的每一個(gè)對象都可以作為一個(gè)鎖。你可能會有疑惑,不對呀,不是還有類鎖嘛。但是  class 對象也是特殊的 Java 對象,所以呢,在 Java 中所有的鎖都是基于對象的

在 Java6  之前,所有的鎖都是"重量級"鎖,重量級鎖會帶來一個(gè)問題,就是如果程序頻繁獲得鎖釋放鎖,就會導(dǎo)致性能的極大消耗。為了優(yōu)化這個(gè)問題,引入了"偏向鎖"和"輕量級鎖"的概念。所以在  Java6 及其以后的版本,一個(gè)對象有 4 種鎖狀態(tài):無鎖狀態(tài),偏向鎖狀態(tài),輕量級鎖狀態(tài),重量級鎖狀態(tài)。

在 4 種鎖狀態(tài)中,無鎖狀態(tài)應(yīng)該比較好理解,無鎖就是沒有鎖,任何線程都可以嘗試修改,所以這里就一筆帶過了。

隨著競爭情況的出現(xiàn),鎖的升級非常容易發(fā)生,但是如果想要讓鎖降級,條件非??量?,有種你想來可以,但是想走不行的趕腳。

阿粉在這里啰嗦一句:很多文章說,鎖如果升級之后是不能降級的,其實(shí)在 HotSpot JVM 中,是支持鎖降級的

鎖降級發(fā)生在 Stop The World 期間,當(dāng) JVM 進(jìn)入安全點(diǎn)的時(shí)候,會檢查有沒有閑置的鎖,如果有就會嘗試進(jìn)行降級

看到 Stop The World 和 安全點(diǎn) 可能有人比較懵,我這里簡單說一下,具體還需要讀者自己去探索一番.(因?yàn)檫@是 JVM  的內(nèi)容,這篇文章的重點(diǎn)不是 JVM )

在 Java 虛擬機(jī)里面,傳統(tǒng)的垃圾回收算法采用的是一種簡單粗暴的方式,就是 Stop-the-world ,而這個(gè) Stop-the-world  就是通過安全點(diǎn)( safepoint )機(jī)制來實(shí)現(xiàn)的,安全點(diǎn)是什么意思呢?就是 Java 程序在執(zhí)行本地代碼時(shí),如果這段代碼不訪問 Java 對象/調(diào)用  Java 方法/返回到原來的 Java 方法,那 Java 虛擬機(jī)的堆棧就不會發(fā)生改變,這就代表執(zhí)行的這段本地代碼可以作為一個(gè)安全點(diǎn)。當(dāng) Java 虛擬機(jī)收到  Stop-the-world 請求時(shí),它會等所有的線程都到達(dá)安全點(diǎn)之后,才允許請求 Stop-the-world 的線程進(jìn)行獨(dú)占工作

怎么理解synchronized與鎖的關(guān)系

接下來就介紹一下幾種鎖和鎖升級

Java 對象頭

在剛開始就說了, Java 的鎖都是基于對象的,那是怎么告訴程序我是個(gè)鎖呢?就不得不來說, Java 對象頭 每個(gè) Java  對象都有對象頭,如果是非數(shù)組類型,就用 2 個(gè)字寬來存儲對象頭,如果是數(shù)組,就用 3 個(gè)字寬來存儲對象頭。在 32 位處理器中,一個(gè)字寬是 32 位;在 64  位處理器中,字寬就是 64 位咯~對象頭的內(nèi)容就是下面這樣:

長度內(nèi)容說明
32/64 bitMark Word存儲對象的 hashCode 或鎖信息等
32/64 bitClass Metadata Address存儲到對象類型數(shù)據(jù)的指針
32/64 bitArray length數(shù)組的長度(如果是數(shù)組)
 

咱們主要來看 Mark Word 的內(nèi)容:

鎖狀態(tài)29 bit/61 bit1 bit 是否是偏向鎖2 bit 鎖標(biāo)志位
無鎖 001
偏向鎖線程 ID101
輕量級鎖指向棧中鎖記錄的指針此時(shí)這一位不用于標(biāo)識偏向鎖00
重量級鎖指向互斥量(重量級鎖)的指針此時(shí)這一位不用于標(biāo)識偏向鎖10
GC 標(biāo)記 此時(shí)這一位不用于標(biāo)識偏向鎖11
 

從上面表格中,應(yīng)該能夠看到,是偏向鎖時(shí), Mark Word 存儲的是偏向鎖的線程 ID ;是輕量級鎖時(shí), Mark Word 存儲的是指向線程棧中  Lock Record 的指針;是重量級鎖時(shí), Mark Word 存儲的是指向堆中的 monitor 對象的指針

偏向鎖

HotSpot 的作者經(jīng)過大量的研究發(fā)現(xiàn),在大多數(shù)情況下,鎖不僅不存在多線程競爭,而且總是由同一線程多次獲得

基于此,就引入了偏向鎖的概念

所以啥是偏向鎖呢?用大白話說就是,我現(xiàn)在給鎖設(shè)置一個(gè)變量,當(dāng)一個(gè)線程請求的時(shí)候,發(fā)現(xiàn)這個(gè)鎖是 true  ,也就是說這個(gè)時(shí)候沒有所謂的資源競爭,那也不用走什么加鎖/解鎖的流程了,直接拿來用就行。但是如果這個(gè)鎖是 false  的話,說明存在其他線程競爭資源,那咱們再走正規(guī)的流程

怎么理解synchronized與鎖的關(guān)系

看一下具體的實(shí)現(xiàn)原理:

當(dāng)一個(gè)線程第一次進(jìn)入同步塊時(shí),會在對象頭和棧幀中的鎖記錄中存儲鎖偏向的線程 ID 。當(dāng)下次該線程進(jìn)入這個(gè)同步塊時(shí),會檢查鎖的  Mark Word 里面存放的是不是自己的線程 ID。如果是,說明線程已經(jīng)獲得了鎖,那么這個(gè)線程在進(jìn)入和退出同步塊時(shí),都不需要花費(fèi) CAS  操作來加鎖和解鎖;如果不是,說明有另外一個(gè)線程來競爭這個(gè)偏向鎖,這時(shí)就會嘗試使用 CAS 來替換 Mark Word 里面的線程 ID 為新線程的 ID  。此時(shí)會有兩種情況:

  • 替換成功,說明之前的線程不存在了,那么 Mark Word 里面的線程 ID 為新線程的 ID ,鎖不會升級,此時(shí)仍然為偏向鎖

  • 替換失敗,說明之前的線程仍然存在,那就暫停之前的線程,設(shè)置偏向鎖標(biāo)識為 0 ,并設(shè)置鎖標(biāo)志位為 00  ,升級為輕量級鎖,按照輕量級鎖的方式進(jìn)行競爭鎖

撤銷偏向鎖

偏向鎖使用了一種等到競爭出現(xiàn)時(shí)才釋放鎖的機(jī)制。也就說,如果沒有人來和我競爭鎖的時(shí)候,那么這個(gè)鎖就是我獨(dú)有的,當(dāng)其他線程嘗試和我競爭偏向鎖時(shí),我會釋放這個(gè)鎖

在偏向鎖向輕量級鎖升級時(shí),首先會暫停擁有偏向鎖的線程,重置偏向鎖標(biāo)識,看起來這個(gè)過程挺簡單的,但是開銷是很大的,因?yàn)?

  • 首先需要在一個(gè)安全點(diǎn)停止擁有鎖的線程

  • 然后遍歷線程棧,如果存在鎖記錄的話,就需要修復(fù)鎖記錄和 Mark Word ,變成無鎖狀態(tài)

  • 最后喚醒被停止的線程,把偏向鎖升級成輕量級鎖

你以為就是升級一個(gè)輕量級鎖?too young too simple

偏向鎖向輕量級鎖升級的過程中,是非常耗費(fèi)資源的,如果應(yīng)用程序中所有的鎖通常都處于競爭狀態(tài),偏向鎖此時(shí)就是一個(gè)累贅,此時(shí)就可以通過 JVM 參數(shù)關(guān)閉偏向鎖:  -XX:-UseBiasedLocking=false ,那么程序默認(rèn)會進(jìn)入輕量級鎖狀態(tài)

最后,來張圖吧~

怎么理解synchronized與鎖的關(guān)系

輕量級鎖

如果多個(gè)線程在不同時(shí)段獲取同一把鎖,也就是不存在鎖競爭的情況,那么 JVM 就會使用輕量級鎖來避免線程的阻塞與喚醒

輕量級鎖加鎖

JVM 會為每個(gè)線程在當(dāng)前線程的棧幀中創(chuàng)建用于存儲鎖記錄的空間,稱之為 Displaced Mark Word  。如果一個(gè)線程獲得鎖的時(shí)候發(fā)現(xiàn)是輕量級鎖,就會將鎖的 Mark Word 復(fù)制到自己的 Displaced Mark Word 中。之后線程會嘗試用 CAS  將鎖的 Mark Word 替換為指向鎖記錄的指針。

如果替換成功,當(dāng)前線程獲得鎖,那么整個(gè)狀態(tài)還是 輕量級鎖 狀態(tài)

如果替換失敗了呢?說明 Mark Word  被替換成了其他線程的鎖記錄,那就嘗試使用自旋來獲取鎖.(自旋是說,線程不斷地去嘗試獲取鎖,一般都是用循環(huán)來實(shí)現(xiàn)的)

自旋是耗費(fèi) CPU 的,如果一直獲取不到鎖,線程就會一直自旋, CPU 那么寶貴的資源就這么被白白浪費(fèi)了

解決這個(gè)問題最簡單的辦法就是指定自旋的次數(shù),比如如果沒有替換成功,那就循環(huán) 10 次,還沒有獲取到,那就進(jìn)入阻塞狀態(tài)

但是 JDK  采用了一個(gè)更加巧妙的方法---適應(yīng)性自旋。就是說,如果這次線程自旋成功了,那我下次自旋次數(shù)更多一些,因?yàn)槲疫@次自旋成功,說明我成功的概率還是挺大的,下次自旋次數(shù)就更多一些,那么如果自旋失敗了,下次我自旋次數(shù)就減少一些,就比如,已經(jīng)看到了失敗的前兆,那我就先溜,而不是非要“不撞南墻不回頭”

自旋失敗之后,線程就會阻塞,同時(shí)鎖會升級成重量級鎖

輕量級鎖釋放:

在釋放鎖時(shí),當(dāng)前線程會使用 CAS 操作將 Displaced Mark Word 中的內(nèi)容復(fù)制到鎖的 Mark Word  里面。如果沒有發(fā)生競爭,這個(gè)復(fù)制的操作就會成功;如果有其他線程因?yàn)樽孕啻螌?dǎo)致輕量級鎖升級成了重量級鎖, CAS  操作就會失敗,此時(shí)會釋放鎖同時(shí)喚醒被阻塞的過程

同樣,來一張圖吧:

怎么理解synchronized與鎖的關(guān)系

重量級鎖

重量級鎖依賴于操作系統(tǒng)的互斥量( mutex  )來實(shí)現(xiàn)。但是操作系統(tǒng)中線程間狀態(tài)的轉(zhuǎn)換需要相對比較長的時(shí)間(因?yàn)椴僮飨到y(tǒng)需要從用戶態(tài)切換到內(nèi)核態(tài),這個(gè)切換成本很高),所以重量級鎖效率很低,但是有一點(diǎn)就是,被阻塞的線程是不會消耗  CPU 的

每一個(gè)對象都可以當(dāng)做一個(gè)鎖,那么當(dāng)多個(gè)線程同時(shí)請求某個(gè)對象鎖時(shí),它會怎么處理呢?

對象鎖會設(shè)置集中狀態(tài)來區(qū)分請求的線程:

Contention List:所有請求鎖的線程將被首先放置到該競爭隊(duì)列

Entry List: Contention List 中那些有資格成為候選人的線程被移到 Entry List 中

Wait Set:調(diào)用 wait 方法被阻塞的線程會被放置到 Wait Set 中

OnDeck:任何時(shí)刻最多只能有一個(gè)線程正在競爭鎖,該線程稱為 OnDeck

Owner:獲得鎖的線程稱為 Owner

!Owner:釋放鎖的線程

當(dāng)一個(gè)線程嘗試獲得鎖時(shí),如果這個(gè)鎖被占用,就會把該線程封裝成一個(gè) ObjectWaiter對象插入到 Contention List 隊(duì)列的隊(duì)首,然后調(diào)用  park 函數(shù)掛起當(dāng)前線程

當(dāng)線程釋放鎖時(shí),會從 Contention List 或者 Entry List 中挑選一個(gè)線程進(jìn)行喚醒

如果線程在獲得鎖之后,調(diào)用了 Object.wait 方法,就會將該線程放入到 WaitSet 中,當(dāng)被 Object.notify 喚醒后,會將線程從  WaitSet 移動到 Contention List 或者 Entry List 中。

但是,當(dāng)調(diào)用一個(gè)鎖對象的 wait 或 notify 方法時(shí),如果當(dāng)前鎖的狀態(tài)是偏向鎖或輕量級鎖,則會先膨脹成重量級鎖

感謝各位的閱讀,以上就是“怎么理解synchronized與鎖的關(guān)系”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對怎么理解synchronized與鎖的關(guān)系這一問題有了更深刻的體會,具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是創(chuàng)新互聯(lián),小編將為大家推送更多相關(guān)知識點(diǎn)的文章,歡迎關(guān)注!


網(wǎng)頁名稱:怎么理解synchronized與鎖的關(guān)系
分享鏈接:http://weahome.cn/article/pjdhgg.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部