目錄
核心思想
Java對象頭(Object Header)與Mark Word
鎖升級的詳細步驟
1. 無鎖(No Lock)
2. 偏向鎖(Biased Locking)
3. 輕量級鎖(Lightweight Lock)
4. 重量級鎖(Heavyweight Lock)
流程圖
總結與意義
這是一個非常重要的概念,它主要是針對?synchronized
?關鍵字在JVM層面的優化,目的是為了在保證線程安全的同時,盡量減少獲取鎖和釋放鎖帶來的性能開銷。
核心思想
鎖升級的核心思想是:JVM會根據實際運行時鎖的競爭情況,動態地將鎖從低開銷的狀態升級為高開銷、高保障的狀態。
在Java早期版本中,synchronized
?的實現非常直接,被稱為“重量級鎖”,性能較差。但從?HotSpot JDK 1.6?開始,JVM團隊對?synchronized
?進行了重大優化,引入了偏向鎖和輕量級鎖,以及鎖升級的概念,使得它的性能得到了極大的提升,現在在很多場景下已經不再比?ReentrantLock
?慢很多。
Java對象頭(Object Header)與Mark Word
要理解鎖升級,首先要知道Java對象在內存中的布局。每個Java對象在堆內存中都有一個對象頭。
對象頭主要包含兩部分:
-
Mark Word:存儲對象自身的運行時數據,如哈希碼、GC分代年齡、鎖狀態標志等。這是鎖升級機制的核心。
-
Klass Pointer:指向對象元數據的指針,JVM通過它來確定對象是哪個類的實例。
鎖的狀態信息就記錄在?Mark Word?中。鎖升級的過程,其實就是Mark Word中內容變化的過程。
鎖升級的詳細步驟
鎖的升級路徑是單向的,從低到高:無鎖 -> 偏向鎖 -> 輕量級鎖 -> 重量級鎖。這個過程是不可逆的。
1. 無鎖(No Lock)
-
狀態:一個新創建的對象,還沒有任何線程來競爭它。
-
Mark Word:存儲對象的哈希碼、分代年齡等信息。鎖標志位為?
01
。
2. 偏向鎖(Biased Locking)
-
設計初衷:在沒有實際競爭或只有一個線程多次使用鎖的場景下,消除整個同步的開銷(比如?
StringBuffer
?的很多方法都是?synchronized
?的,但通常只會在單線程中使用)。 -
工作原理:當第一個線程訪問同步塊時,JVM會將鎖置為偏向模式,并在Mark Word中記錄這個線程的ID。之后這個線程再次進入和退出同步塊時,不需要進行任何CAS操作來加鎖和解鎖,只需要簡單檢查一下Mark Word中是否存儲著自己的線程ID。
-
升級觸發條件:一旦有另一個線程來嘗試獲取這個鎖(發生了競爭),偏向鎖就會失效。
-
鎖標志位:
01
?(同時有額外位標識是否為偏向模式)。
可以把它想象成“貼標簽”:第一個線程來了,在對象上貼了個標簽“此物歸我所有”。以后它再來,看到自己的標簽就直接用了。直到有另一個人也想來用,標簽就被撕掉。
注意:由于維護偏向鎖本身也有開銷,且在實際多線程環境中,真正的無競爭場景并不多,從JDK 15開始,偏向鎖已被默認禁用。但理解它對于理解整個鎖升級體系依然至關重要。
3. 輕量級鎖(Lightweight Lock)
-
設計初衷:當鎖確實存在競爭,但競爭的激烈程度很低(即線程幾乎是交替執行,沒有同時搶鎖),避免直接使用重量級鎖帶來的巨大開銷。
-
工作原理:
-
當線程要獲取鎖時,JVM會在當前線程的棧幀中創建一個名為鎖記錄(Lock Record)的空間。
-
將對象頭的Mark Word復制到鎖記錄中(稱為Displaced Mark Word)。
-
線程嘗試使用CAS操作將對象頭的Mark Word替換為指向該鎖記錄的指針。
-
如果CAS成功,當前線程就獲得了鎖,此時鎖標志位變為?
00
。 -
如果CAS失敗(說明其他線程已經在競爭這個鎖了),當前線程會嘗試自旋(循環重試)來獲取鎖。
-
-
升級觸發條件:如果自旋了一定次數后(JVM有自適應策略,自旋次數不固定)還沒有獲得鎖,或者自旋期間又有第三個線程來競爭,輕量級鎖就會膨脹為重量級鎖。
-
鎖標志位:
00
可以把它想象成“搶凳子”:大家(線程)禮貌地轉著圈(自旋)等凳子(鎖),誰先坐下誰就用。但如果等太久或者人太多,游戲規則就變了。
4. 重量級鎖(Heavyweight Lock)
-
設計初衷:處理高激烈度的鎖競爭場景。
-
工作原理:此時鎖會向操作系統內核申請互斥量(Mutex)。未能獲取到鎖的線程會被掛起(進入阻塞狀態),并放入一個等待隊列中。當鎖被釋放時,操作系統會負責喚醒等待隊列中的線程,讓它們重新競爭鎖。
-
開銷:這個過程中涉及用戶態到內核態的切換、線程的掛起和喚醒,是開銷最大的一種鎖狀態。
-
鎖標志位:
10
可以把它想象成“正式排隊”:沒拿到鎖的人不再自己傻等,而是去休息室(等待隊列)睡覺(阻塞),由管理員(操作系統)叫號喚醒。
流程圖
下圖清晰地展示了鎖升級的全過程:
總結與意義
-
優化目的:鎖升級是一種“按需付費”的優化機制。JVM會根據競爭的激烈程度,從低開銷的方案開始嘗試,只有在不得已時才會使用開銷最大的方案。這極大地提高了?
synchronized
?在常見場景下的性能。 -
不可逆性:鎖只能升級,不能降級。這是為了節省在激烈競爭環境下不必要的降級開銷。
-
實際應用:對于開發者來說,這個過程是完全透明的,由JVM自動完成。我們只需要安心使用?
synchronized
?關鍵字,JVM會在背后為我們選擇最優的鎖方案。 -
與ReentrantLock的對比:
ReentrantLock
?的實現更類似于“一開始就是重量級鎖”的思路(雖然它也在用戶態做了很多優化,如CAS自旋),但它提供了更靈活的功能(如可中斷、公平鎖等)。而?synchronized
?的優勢在于語法簡潔和JVM的自動優化。