免费观看又色又爽又黄的小说免费_美女福利视频国产片_亚洲欧美精品_美国一级大黄大色毛片

怎么理解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è)計、網(wǎng)站建設(shè)、微信開發(fā)、成都小程序開發(fā)、集團(tuán)企業(yè)網(wǎng)站建設(shè)等服務(wù)項目。為回饋新老客戶創(chuàng)新互聯(lián)還提供了彌渡免費(fèi)建站歡迎大家使用!

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

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

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

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

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

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

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

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

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

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

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

在 Java6  之前,所有的鎖都是"重量級"鎖,重量級鎖會帶來一個問題,就是如果程序頻繁獲得鎖釋放鎖,就會導(dǎo)致性能的極大消耗。為了優(yōu)化這個問題,引入了"偏向鎖"和"輕量級鎖"的概念。所以在  Java6 及其以后的版本,一個對象有 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)的時候,會檢查有沒有閑置的鎖,如果有就會嘗試進(jìn)行降級

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

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

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

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

Java 對象頭

在剛開始就說了, Java 的鎖都是基于對象的,那是怎么告訴程序我是個鎖呢?就不得不來說, Java 對象頭 每個 Java  對象都有對象頭,如果是非數(shù)組類型,就用 2 個字寬來存儲對象頭,如果是數(shù)組,就用 3 個字寬來存儲對象頭。在 32 位處理器中,一個字寬是 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
輕量級鎖指向棧中鎖記錄的指針此時這一位不用于標(biāo)識偏向鎖00
重量級鎖指向互斥量(重量級鎖)的指針此時這一位不用于標(biāo)識偏向鎖10
GC 標(biāo)記 此時這一位不用于標(biāo)識偏向鎖11
 

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

偏向鎖

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

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

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

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

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

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

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

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

撤銷偏向鎖

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

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

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

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

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

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

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

最后,來張圖吧~

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

輕量級鎖

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

輕量級鎖加鎖

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

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

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

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

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

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

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

輕量級鎖釋放:

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

同樣,來一張圖吧:

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

重量級鎖

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

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

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

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

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

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

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

Owner:獲得鎖的線程稱為 Owner

!Owner:釋放鎖的線程

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

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

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

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

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

文章名稱:怎么理解synchronized與鎖的關(guān)系
分享路徑:http://m.newbst.com/article16/pjdhgg.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供微信公眾號、品牌網(wǎng)站制作網(wǎng)站設(shè)計、ChatGPT、定制開發(fā)Google

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

綿陽服務(wù)器托管