Java如何避免重量级锁,Java 中锁是如何一步步膨胀的(偏向锁、轻量级锁、重量级锁)...
文章目錄
重量級鎖(Mutex Lock)
偏向鎖(比較 ThreadID)
偏向鎖獲取過程
偏向鎖的釋放
輕量級鎖(自旋)
輕量級鎖的加鎖過程
輕量級鎖的釋放
總結
重量級鎖(Mutex Lock)
Synchronized 是通過對象內部的一個叫做監視器鎖(monitor)來實現的。但是監視器鎖本質又是依賴于底層的操作系統的 Mutex Lock 來實現的。而操作系統實現線程之間的切換這就需要從用戶態轉換到核心態,這個成本非常高,狀態之間的轉換需要相對比較長的時間,這就是為什么 Synchronized 效率低的原因。因此,這種依賴于操作系統 Mutex Lock 所實現的鎖我們稱之為“重量級鎖”。JDK 中對 Synchronized 做的種種優化,其核心都是為了減少這種重量級鎖的使用。JDK1.6 以后,為了減少獲得鎖和釋放鎖所帶來的性能消耗,提高性能,引入了自旋鎖、自適應自旋鎖、輕量級鎖和偏向鎖。
明確 Java 線程切換的代價,是理解java中各種鎖的優缺點的基礎之一。
Java 對象頭中 markword 結構。Java 對象的內存布局及訪問方式
偏向鎖(比較 ThreadID)
Hotspot 的作者經過以往的研究發現大多數情況下鎖不僅不存在多線程競爭,而且總是由同一線程多次獲得。偏向鎖的目的是在某個線程獲得鎖之后,消除這個線程鎖重入(CAS)的開銷,看起來讓這個線程得到了偏護。引入偏向鎖是為了在無多線程競爭的情況下盡量減少不必要的輕量級鎖執行路徑,因為輕量級鎖的獲取及釋放依賴多次 CAS 原子指令,而偏向鎖只需要在置換 ThreadID 的時候依賴一次 CAS 原子指令(由于一旦出現多線程競爭的情況就必須撤銷偏向鎖,所以偏向鎖的撤銷操作的性能損耗必須小于節省下來的 CAS 原子指令的性能消耗)。輕量級鎖是為了在線程交替執行同步塊時提高性能,而偏向鎖則是在只有一個線程執行同步塊時進一步提高性能。
偏向鎖獲取過程
訪問 Mark Word 中鎖標志位是否為 01,是的話查看偏向鎖的標識,如果是 1,則確認為可偏向狀態;如果是 0 則為無鎖狀態,直接通過 CAS 操作競爭鎖,如果競爭失敗,執行4。
如果為可偏向狀態,則測試線程 ID 是否指向當前線程,如果是,進入步驟5,否則進入步驟3。
如果線程 ID 并未指向當前線程,則通過 CAS 操作競爭鎖。如果競爭成功,則將 Mark Word 中線程 ID 設置為當前線程 ID,然后執行5;如果競爭失敗,執行4。
如果 CAS 獲取偏向鎖失敗,則表示有競爭,開始鎖撤銷。
執行同步代碼。
偏向鎖的釋放
偏向鎖使用了一種等到競爭出現才釋放鎖的機制,所以當其他線程嘗試競爭偏向鎖時,持有偏向鎖的線程才會釋放鎖,線程不會主動去釋放偏向鎖。
當獲得偏向鎖的線程到達全局安全點(safepoint)時暫停該線程,檢查該線程的狀態。
如果該線程存活且沒有退出同步代碼塊,則升級為輕量級鎖,并喚醒該線程從安全點繼續執行。
如果該線程沒有存活或者該線程已退出同步代碼塊,則將偏向鎖撤銷為無鎖狀態(鎖標志位 01,偏向鎖標識 0)喚醒該線程。
到達安全點 safepoint 會導致 stop the word,時間很短。
輕量級鎖(自旋)
輕量級是相對于使用操作系統互斥量來實現的傳統鎖而言的。但是,首先需要強調一點的是,輕量級鎖并不是用來代替重量級鎖的,它的本意是在沒有多線程競爭的前提下,減少傳統的重量級鎖使用產生的性能消耗。在解釋輕量級鎖的執行過程之前,先明白一點,輕量級鎖所適應的場景是線程交替執行同步塊的情況,如果存在同一時間訪問同一鎖的情況,就會導致輕量級鎖膨脹為重量級鎖。
輕量級鎖的加鎖過程
1、訪問對象的 Mark Word 中鎖標識位,如果為 00,JVM 會先在當前線程的棧幀中建立一個名為鎖記錄(Lock Record)的空間,用于存儲鎖對象目前的 Mark Word 的拷貝,官方稱之為 Displaced Mark Word。這時候線程堆棧與對象頭的狀態如圖所示。(注:該圖僅作為參考,我認為該圖可能存在問題)
2、將對象頭中的 Mark Word 復制到鎖記錄中;
3、拷貝成功后,虛擬機將使用 CAS 操作嘗試將對象的 Mark Word 更新為指向 Lock Record 的指針,并將 Lock Record 里的 owner 指針指向 object mark word。如果更新成功,則執行步驟4,否則執行步驟5。
4、如果這個更新動作成功了,那么這個線程就擁有了該對象的鎖,此時 Mark Word 的鎖標識為 00,這時候線程堆棧與對象頭的狀態如圖所示。(注:該圖僅作為參考,我認為該圖可能存在問題)
5、如果這個更新操作失敗了,虛擬機首先會檢查對象的 Mark Word 是否指向當前線程的棧幀,如果是就說明當前線程已經擁有了這個對象的鎖,那就可以直接進入同步塊繼續執行。否則當前線程便嘗試使用自旋來獲取鎖(自旋就是為了不讓線程阻塞,而采用循環去獲取鎖的過程),自旋達到一定次數后 CAS 操作依然沒有成功,輕量級鎖就要膨脹為重量級鎖,鎖標識設置為 10,Mark Word 中存儲的就是指向重量級鎖(monitor)的指針,當前線程以及后面等待鎖的線程便會進入阻塞狀態。
輕量級鎖的釋放
輕量級鎖解鎖時,持有鎖的線程會使用 CAS 原子操作將 Mark Word 替換回到對象頭,如果成功,則表示沒有競爭發生。如果失敗,釋放鎖并喚醒那些被掛起的線程。
總結
偏向鎖
偏向鎖只會在第一次請求鎖時使用 CAS 操作,并在鎖對象的標記字段中記錄當前線程 ID。在此后的運行過程中,僅需比較線程 ID,消除這個線程鎖重入(CAS)的開銷。針對的是鎖僅會被同一線程持有的狀況。
輕量級鎖
輕量級鎖采用 CAS 操作,減少了傳統的重量級鎖使用產生的性能消耗。針對的是多個線程在不同時間段申請同一把鎖的情況。
重量級鎖
重量級鎖會阻塞、喚醒請求加鎖的線程,會導致線程上下文切換。針對的是多個線程同時競爭同一把鎖的情況。JVM 采用自適應自旋,來避免在面對非常小的同步代碼塊時,仍會被阻塞和喚醒的狀況。
參考文章:
https://blog.csdn.net/zhao_miao/article/details/84500771
https://blog.csdn.net/zqz_zqz/article/details/70233767
總結
以上是生活随笔為你收集整理的Java如何避免重量级锁,Java 中锁是如何一步步膨胀的(偏向锁、轻量级锁、重量级锁)...的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 鼻炎手术多少钱啊?
- 下一篇: 地下城与勇士怎么做那个勇者装备啊?