偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

這回,要將 synchronized 與鎖的關(guān)系講透?。?!

開發(fā) 后端
說到鎖,都會(huì)提 synchronized 。這個(gè)英文單詞兒啥意思呢?翻譯成中文就是「同步」的意思.一般都是使用 synchronized 這個(gè)關(guān)鍵字來給一段代碼或者一個(gè)方法上鎖,使得這段代碼或者方法,在同一個(gè)時(shí)刻只能有一個(gè)線程來執(zhí)行它。

 說到鎖,都會(huì)提 synchronized 。這個(gè)英文單詞兒啥意思呢?翻譯成中文就是「同步」的意思

一般都是使用 synchronized 這個(gè)關(guān)鍵字來給一段代碼或者一個(gè)方法上鎖,使得這段代碼或者方法,在同一個(gè)時(shí)刻只能有一個(gè)線程來執(zhí)行它。

[[324960]]

synchronized 相比于 volatile 來說,用的比較靈活,你可以在方法上使用,可以在靜態(tài)方法上使用,也可以在代碼塊上使用。

關(guān)于 synchronized 這一塊大概就說到這里,阿粉今天想著重來說一下, synchronized 底層是怎么實(shí)現(xiàn)的

 

 

 

[[324961]]

 

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

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

 

  1. public class demo { 
  2.     public void synchronizedDemo(Object lock){ 
  3.   synchronized(lock){ 
  4.    lock.hashCode(); 
  5.   } 
  6.  } 

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

 

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

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

  • 關(guān)于 monitorenter 和 monitorexit ,可以理解為每個(gè)鎖對(duì)象擁有一個(gè)鎖計(jì)數(shù)器和一個(gè)指向持有該鎖的線程指針:
  • 當(dāng)程序執(zhí)行 monitorenter 時(shí),如果目標(biāo)鎖對(duì)象的計(jì)數(shù)器為 0 ,說明這個(gè)時(shí)候它沒有被其他線程所占有,此時(shí)如果有線程來請(qǐng)求使用, Java 虛擬機(jī)就會(huì)分配給該線程,并且把計(jì)數(shù)器的值加 1
  • 目標(biāo)鎖對(duì)象計(jì)數(shù)器不為 0 時(shí),如果鎖對(duì)象持有的線程是當(dāng)前線程, Java 虛擬機(jī)可以將其計(jì)數(shù)器加 1 ,如果不是呢?那很抱歉,就只能等待,等待持有線程釋放掉

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

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

 

 

 

[[324962]]

 

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

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

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

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

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

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

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

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

 

 

 

[[324963]]

 

接下來就介紹一下幾種鎖和鎖升級(jí)

Java 對(duì)象頭

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

長(zhǎng)度 內(nèi)容 說明
32/64 bit Mark Word 存儲(chǔ)對(duì)象的 hashCode 或鎖信息等
32/64 bit Class Metadata Address 存儲(chǔ)到對(duì)象類型數(shù)據(jù)的指針
32/64 bit Array length 數(shù)組的長(zhǎng)度(如果是數(shù)組)
 

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

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

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

偏向鎖

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

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

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

 

 

 

[[324964]]

 

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

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

  • 替換成功,說明之前的線程不存在了,那么 Mark Word 里面的線程 ID 為新線程的 ID ,鎖不會(huì)升級(jí),此時(shí)仍然為偏向鎖
  • 替換失敗,說明之前的線程仍然存在,那就暫停之前的線程,設(shè)置偏向鎖標(biāo)識(shí)為 0 ,并設(shè)置鎖標(biāo)志位為 00 ,升級(jí)為輕量級(jí)鎖,按照輕量級(jí)鎖的方式進(jìn)行競(jìng)爭(zhēng)鎖

撤銷偏向鎖

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

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

  • 首先需要在一個(gè)安全點(diǎn)停止擁有鎖的線程
  • 然后遍歷線程棧,如果存在鎖記錄的話,就需要修復(fù)鎖記錄和 Mark Word ,變成無鎖狀態(tài)
  • 最后喚醒被停止的線程,把偏向鎖升級(jí)成輕量級(jí)鎖

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

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

最后,來張圖吧~

 

 

 

 

輕量級(jí)鎖

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

輕量級(jí)鎖加鎖

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

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

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

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

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

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

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

輕量級(jí)鎖釋放:

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

同樣,來一張圖吧:

 

 

 

 

重量級(jí)鎖

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

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

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

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

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

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

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

Owner:獲得鎖的線程稱為 Owner

!Owner:釋放鎖的線程

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

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

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

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

總結(jié):

synchronized 關(guān)鍵字是通過 monitorenter 和 monitorexit 兩種指令來保證鎖的

當(dāng)一個(gè)線程準(zhǔn)備獲取共享資源時(shí):

  • 首先檢查 MarkWord 里面放的是不是自己的 ThreadID ,如果是,說明當(dāng)前線程處于 "偏向鎖"
  • 如果不是,鎖升級(jí),這時(shí)使用 CAS 操作來執(zhí)行切換,新的線程根據(jù) MarkWord 里面現(xiàn)有的 ThreadID 來通知之前的線程暫停,將 MarkWord 的內(nèi)容置為空
  • 然后,兩個(gè)線程都將鎖對(duì)象 HashCode 復(fù)制到自己新建的用于存儲(chǔ)鎖的記錄空間中,接著開始通過 CAS 操作,把鎖對(duì)象的 MarkWord 的內(nèi)容修改為自己新建的記錄空間地址,以這種方式競(jìng)爭(zhēng) MarkWord ,成功執(zhí)行 CAS 的線程獲得資源,失敗的則進(jìn)入自旋
  • 自旋的線程在自旋過程中,如果成功獲得資源(也就是之前獲得資源的線程執(zhí)行完畢,釋放了共享資源),那么整個(gè)狀態(tài)依然是 輕量級(jí)鎖 的狀態(tài)
  • 如果沒有獲得資源,就進(jìn)入 重量級(jí)鎖 的狀態(tài),此時(shí),自旋的線程進(jìn)行阻塞,等待之前線程執(zhí)行完成并且喚醒自己

參考:

  • Java 并發(fā)編程的技術(shù)
  • 極客時(shí)間---深入拆解 Java 虛擬機(jī)

到這里,整篇文章的內(nèi)容就算是結(jié)束了。

沒想到這篇文章竟然被阿粉寫了有 5000 多字(阿粉不會(huì)告訴你這篇文章,是阿粉在假期里面從大早上八點(diǎn)就開始寫,寫到下午五六點(diǎn),累到虛脫的我

能夠閱讀到這里的各位,都是最靚的仔仔

[[324965]]

 

 

責(zé)任編輯:武曉燕 來源: Java極客技術(shù)
相關(guān)推薦

2020-12-01 11:34:14

Elasticsear

2020-03-26 09:18:54

高薪本質(zhì)因素

2022-07-04 08:01:01

鎖優(yōu)化Java虛擬機(jī)

2025-01-13 12:00:00

反射Java開發(fā)

2025-08-08 00:00:00

2024-08-13 14:08:25

2021-07-28 08:32:58

Go并發(fā)Select

2024-09-06 12:52:59

2020-03-12 09:02:34

數(shù)據(jù)思維統(tǒng)計(jì)學(xué)大數(shù)據(jù)

2023-11-09 08:41:25

DevOpsAIOps軟件

2023-06-12 08:49:12

RocketMQ消費(fèi)邏輯

2021-12-16 18:38:13

面試Synchronize

2023-11-08 08:18:19

鎖升級(jí)多線程

2021-07-01 09:43:44

Python函數(shù)參數(shù)

2024-08-13 17:09:00

架構(gòu)分庫分表開發(fā)

2021-05-31 08:00:00

消息隊(duì)列架構(gòu)Rabbit MQ

2025-09-30 02:00:00

2024-06-27 08:55:41

2022-03-08 08:44:13

偏向鎖Java內(nèi)置鎖

2011-11-28 12:31:20

JavaJVM
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)