Java synchronized從使用到底層鎖升級機制詳解

在Java并發(fā)編程中,synchronized是最基礎(chǔ)也最核心的鎖機制——它使用簡單(加個關(guān)鍵字就能保證線程安全),但底層原理卻藏著大量面試高頻考點:
- 只會用
synchronized修飾方法,說不清楚“實例鎖”和“類鎖”的區(qū)別; - 被追問“
synchronized底層怎么實現(xiàn)?”,只知道“Monitor”卻講不出細節(jié); - 分不清“偏向鎖、輕量級鎖、重量級鎖”的升級邏輯,答不出JDK 1.6的優(yōu)化點;
- 面試被問“為什么
synchronized是可重入的?”“高并發(fā)下偏向鎖要不要關(guān)閉?”當場卡殼。
本文從使用場景→字節(jié)碼原理→底層結(jié)構(gòu)→鎖升級流程→面試考點層層拆解,結(jié)合圖解、代碼案例、源碼片段,徹底講透synchronized的核心邏輯:
? 3種使用方式+字節(jié)碼層面解析,分清實例鎖/類鎖;
? 圖解對象頭(Mark Word)結(jié)構(gòu),看懂鎖狀態(tài)的存儲邏輯;
? 動態(tài)拆解“無鎖→偏向鎖→輕量級鎖→重量級鎖”升級全過程;
? JDK 1.6鎖優(yōu)化:自適應自旋、鎖消除、鎖粗化(面試加分點);
? 10+高頻面試題標準答案(直接背)。
?? 核心一句話:
synchronized基于對象監(jiān)視器(Monitor)實現(xiàn),JDK 1.6為解決性能問題引入“鎖升級”機制,從無鎖逐步升級為偏向鎖、輕量級鎖、重量級鎖,僅在高競爭場景下才會觸發(fā)重量級鎖(依賴OS互斥量),大幅提升低競爭場景的性能。
?? 面試金句先記牢:
- synchronized的三種用法:實例鎖(this)、類鎖(Class對象)、代碼塊鎖(任意對象);
- 鎖升級是不可逆的單向過程(偏向鎖→輕量級鎖→重量級鎖),核心依賴對象頭Mark Word存儲鎖狀態(tài);
- JDK 1.6優(yōu)化后,
synchronized不再是“重量級鎖”的代名詞,低競爭場景下性能接近CAS;synchronized的可重入性依賴Monitor的計數(shù)器機制,每加鎖一次計數(shù)器+1,解鎖一次-1,為0時釋放鎖。- 字節(jié)碼層面通過
monitorenter和monitorexit指令實現(xiàn);
一、基礎(chǔ)篇:synchronized的3種使用方式(避坑版)
synchronized的使用看似簡單,但90%的開發(fā)者會混淆“實例鎖”和“類鎖”,導致線程安全問題。先從使用方式入手,夯實基礎(chǔ)。

1.1 修飾實例方法(實例鎖)
鎖對象是當前實例(this),不同實例之間的鎖互不干擾。
public class SyncInstanceMethod {
// 實例鎖:鎖對象是this
public synchronized void doSomething() {
System.out.println("實例方法鎖:" + Thread.currentThread().getName());
try {
Thread.sleep(1000); // 模擬耗時操作
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
public static void main(String[] args) {
SyncInstanceMethod instance1 = new SyncInstanceMethod();
SyncInstanceMethod instance2 = new SyncInstanceMethod();
// 線程1:調(diào)用instance1的同步方法(鎖instance1)
new Thread(() -> instance1.doSomething(), "線程1").start();
// 線程2:調(diào)用instance2的同步方法(鎖instance2)→ 不會阻塞
new Thread(() -> instance2.doSomething(), "線程2").start();
}
}結(jié)果:線程1和線程2同時執(zhí)行(不同實例,鎖不互斥)。
1.2 修飾靜態(tài)方法(類鎖)
鎖對象是類的Class對象(如SyncStaticMethod.class),所有實例共享同一把鎖。

public class SyncStaticMethod {
// 類鎖:鎖對象是SyncStaticMethod.class
public static synchronized void doStaticSomething() {
System.out.println("靜態(tài)方法鎖:" + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
public static void main(String[] args) {
SyncStaticMethod instance1 = new SyncStaticMethod();
SyncStaticMethod instance2 = new SyncStaticMethod();
// 線程1:調(diào)用靜態(tài)方法(鎖Class對象)
new Thread(() -> instance1.doStaticSomething(), "線程1").start();
// 線程2:調(diào)用靜態(tài)方法(同一把類鎖)→ 阻塞
new Thread(() -> instance2.doStaticSomething(), "線程2").start();
}
}結(jié)果:線程2等待線程1執(zhí)行完后才執(zhí)行(類鎖全局唯一)。
1.3 修飾代碼塊(自定義鎖對象)
靈活指定鎖對象,是最常用的方式(縮小鎖范圍,提升性能)。

public class SyncCodeBlock {
// 自定義鎖對象(推薦使用final,避免鎖對象被修改)
private final Object lock = new Object();
// 類鎖的代碼塊寫法
private static final Object classLock = new Object();
public void doBlockSomething() {
// 鎖自定義對象
synchronized (lock) {
System.out.println("代碼塊鎖(實例):" + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
public static void doStaticBlockSomething() {
// 鎖Class對象(等價于靜態(tài)方法鎖)
synchronized (SyncCodeBlock.class) {
System.out.println("代碼塊鎖(類):" + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
public static void main(String[] args) {
SyncCodeBlock instance = new SyncCodeBlock();
// 同一實例的代碼塊鎖互斥
new Thread(() -> instance.doBlockSomething(), "線程1").start();
new Thread(() -> instance.doBlockSomething(), "線程2").start();
}
}1.4 核心避坑點
| 錯誤用法 | 問題本質(zhì) | 正確做法 |
|---|---|---|
鎖字符串常量(如synchronized ("lock")) | 字符串常量池復用,導致不同業(yè)務共享同一把鎖 | 使用自定義final Object作為鎖對象 |
鎖局部變量(如synchronized (new Object())) | 每次創(chuàng)建新對象,鎖失效(線程不互斥) | 鎖成員變量或類變量 |
| 實例鎖和類鎖混用 | 兩者是不同鎖對象,無法保證線程安全 | 明確鎖粒度,統(tǒng)一使用實例鎖或類鎖 |

二、原理篇:字節(jié)碼+Monitor,看懂synchronized的底層實現(xiàn)
要理解鎖升級,先搞懂synchronized最基礎(chǔ)的實現(xiàn)邏輯——字節(jié)碼指令和Monitor機制。

2.1 字節(jié)碼層面:monitorenter/monitorexit指令
編譯后的synchronized代碼塊會生成monitorenter和monitorexit指令,修飾方法則會在方法表中標記ACC_SYNCHRONIZED。
代碼示例
public class SyncByteCode {
private final Object lock = new Object();
public void test() {
synchronized (lock) {
System.out.println("synchronized代碼塊");
}
}
}核心字節(jié)碼(關(guān)鍵部分)
執(zhí)行:
javap -c SyncByteCode.class
字節(jié)碼信息如下:
// synchronized (lock) 對應的字節(jié)碼 0 aload_0 1 getfield #2 // 獲取lock對象 4 dup 5 astore_1 6 monitorenter // 進入Monitor,獲取鎖 7 getstatic #3 // System.out 10 ldc #4 // 字符串"synchonized代碼塊" 12 invokevirtual #5 // 執(zhí)行println 15 aload_1 16 monitorexit // 退出Monitor,釋放鎖 17 goto 25 20 astore_2 21 aload_1 22 monitorexit // 異常時的monitorexit(保證鎖釋放) 23 aload_2 24 athrow 25 return
指令解析
monitorenter:嘗試獲取對象的Monitor所有權(quán),成功則計數(shù)器+1,失敗則線程阻塞;monitorexit:釋放Monitor所有權(quán),計數(shù)器-1,當計數(shù)器為0時,完全釋放鎖;- 編譯器會生成兩個
monitorexit:一個正常退出,一個異常退出(保證鎖最終釋放,避免死鎖)。
2.2 Monitor(對象監(jiān)視器):synchronized的核心依賴
每個Java對象都關(guān)聯(lián)一個Monitor(C++實現(xiàn),ObjectMonitor類),其核心結(jié)構(gòu)如下:

- _Owner:當前持有鎖的線程,初始為null;
- _EntryList:未獲取鎖的線程進入該隊列,處于BLOCKED狀態(tài);
- _WaitSet:調(diào)用
wait()的線程進入該隊列,處于WAITING狀態(tài); - _count:可重入計數(shù)器,初始為0,每加鎖一次+1,解鎖一次-1。

2.3 基礎(chǔ)實現(xiàn)流程
- 線程執(zhí)行
monitorenter時,嘗試將Monitor的_Owner設(shè)為當前線程:- 成功:
_count+1,執(zhí)行同步代碼塊; - 失?。哼M入
_EntryList阻塞,等待鎖釋放。
- 成功:
- 線程執(zhí)行
monitorexit時,_count-1:- 若
_count=0:釋放鎖(_Owner設(shè)為null),喚醒_EntryList中的線程競爭鎖; - 若
_count>0:僅減少計數(shù)器(可重入特性)。
- 若
三、核心篇:對象頭Mark Word與鎖狀態(tài)存儲
鎖升級的核心是對象頭(Object Header),其中的Mark Word字段存儲了鎖狀態(tài)、線程ID等關(guān)鍵信息,先看懂Mark Word的結(jié)構(gòu),才能理解鎖升級。
3.1 Java對象的內(nèi)存布局
每個Java對象在內(nèi)存中分為3部分:
┌─────────────────────────────────────────────────────────┐ │ Object Header (對象頭) │ │ ├─────────────────────────────────────────────────────┤ │ │ │ Mark Word (標記字段):存儲鎖狀態(tài)、哈希值、線程ID等 │ │ │ ├─────────────────────────────────────────────────────┤ │ │ │ Klass Pointer (類型指針):指向類的Class對象 │ │ │ └─────────────────────────────────────────────────────┘ │ ├─────────────────────────────────────────────────────────┤ │ Instance Data (實例數(shù)據(jù)):存儲對象的成員變量 │ ├─────────────────────────────────────────────────────────┤ │ Padding (對齊填充):保證對象大小為8字節(jié)的整數(shù)倍 │ └─────────────────────────────────────────────────────────┘
- Mark Word:核心字段,長度為32位(32位JVM)或64位(64位JVM),動態(tài)存儲不同信息(根據(jù)鎖狀態(tài));
- Klass Pointer:默認占4字節(jié)(32位)/8字節(jié)(64位),指向?qū)ο蟮念愒獢?shù)據(jù);
- Padding:僅為內(nèi)存對齊,無業(yè)務意義。

3.2 Mark Word的詳細布局(64位VM為例)
Mark Word在不同鎖狀態(tài)下存儲的內(nèi)容完全不同 :
| 鎖狀態(tài) | 25bit | 31bit | 1bit | 4bit | 1bit(偏向鎖) | 2bit(鎖標志) |
|---|---|---|---|---|---|---|
| 無鎖 | unused | hashCode | unused | 分代年齡 | 0 | 01 |
| 偏向鎖 | threadId(54bit) | epoch | 分代年齡 | 1 | 01 | |
| 輕量級鎖 | 指向棧中鎖記錄(Lock Record)的指針(62bit) | 00 | ||||
| 重量級鎖 | 指向Monitor對象的指針(62bit) | 10 | ||||
| GC標記 | 空(不存信息) | 11 |
?? 關(guān)鍵:
- 鎖狀態(tài)由最后2位標識:01=無鎖/偏向鎖,00=輕量級鎖,10=重量級鎖,11=GC標記;
- 偏向鎖和無鎖的鎖狀態(tài)都是01,通過是否偏向鎖位(第6位) 區(qū)分;
- Mark Word的動態(tài)復用是鎖升級的核心基礎(chǔ)。

3.3 圖解:Mark Word狀態(tài)切換
┌─────────────────────────────────────────────────────────┐ │ 無鎖(001) │ │ ┌─────────────────────────────────────┐ │ │ │ hashCode(25/31bit)│ age │ 0│ 01 │ │ └─────────────────────────────────────┘ │ │ ↓ 第一個線程獲取鎖 │ ├─────────────────────────────────────────────────────────┤ │ 偏向鎖(101) │ │ ┌─────────────────────────────────────┐ │ │ │ threadId(54bit)│epoch │age│ 1│ 01 │ │ └─────────────────────────────────────┘ │ │ ↓ 第二個線程競爭 │ ├─────────────────────────────────────────────────────────┤ │ 輕量級鎖(00) │ │ ┌─────────────────────────────────────┐ │ │ │ 指向Lock Record的指針(62bit) │ 00 │ │ └─────────────────────────────────────┘ │ │ ↓ 自旋失敗/競爭加劇 │ ├─────────────────────────────────────────────────────────┤ │ 重量級鎖(10) │ │ ┌─────────────────────────────────────┐ │ │ │ 指向Monitor對象的指針(62bit) │ 10 │ │ └─────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────┘
四、進階篇:鎖升級全過程(無鎖→偏向鎖→輕量級鎖→重量級鎖)
JDK 1.5及之前,synchronized直接使用重量級鎖(依賴OS互斥量mutex),每次加鎖解鎖都需要從用戶態(tài)切換到內(nèi)核態(tài),開銷巨大,性能極差;
JDK 1.6引入“鎖升級”機制,僅在高競爭場景下才觸發(fā)重量級鎖,大幅提升性能。

核心前提
- 鎖升級是單向不可逆的:無鎖 → 偏向鎖 → 輕量級鎖 → 重量級鎖;
- 偏向鎖默認開啟(JDK 1.6+),啟動后有4秒延遲(可通過JVM參數(shù)關(guān)閉);
- 鎖升級的觸發(fā)條件是線程競爭:無競爭→偏向鎖,輕度競爭→輕量級鎖,重度競爭→重量級鎖。
4.1 第一步:無鎖狀態(tài)
- 對象剛創(chuàng)建時,Mark Word存儲哈希值、分代年齡,是否偏向鎖位=0,鎖狀態(tài)=01;
- 無任何線程競爭,無需加鎖。
4.2 第二步:偏向鎖(無競爭場景)
觸發(fā)條件
第一個線程獲取鎖,且無其他線程競爭。
核心邏輯(消除CAS,提升性能)
- 線程獲取鎖時,JVM將Mark Word的是否偏向鎖位設(shè)為1,并把當前線程ID寫入Mark Word;
- 該線程后續(xù)再次獲取鎖時,只需檢查Mark Word中的線程ID是否為自身:
- 是:直接進入同步代碼塊(無需CAS,無需操作系統(tǒng)介入);
- 否:觸發(fā)偏向鎖撤銷/升級。
- 偏向鎖的釋放不主動釋放,僅在其他線程競爭時才會撤銷。
偏向鎖的優(yōu)勢
消除了輕量級鎖的CAS操作,是JDK 1.6對synchronized最核心的優(yōu)化——低競爭場景下,偏向鎖的性能接近無鎖。
4.3 第三步:輕量級鎖(輕度競爭)
觸發(fā)條件
有其他線程競爭偏向鎖,且當前持有鎖的線程仍在執(zhí)行(未釋放)。
核心邏輯(CAS自旋,避免OS阻塞)
- 競爭線程嘗試通過CAS將Mark Word中的指針指向自己的棧幀中的“鎖記錄(Lock Record)”:
- 成功:獲取輕量級鎖,鎖狀態(tài)改為00;
- 失敗:自旋(循環(huán)重試CAS),自旋次數(shù)達到閾值后升級為重量級鎖。
- 輕量級鎖的釋放:通過CAS將Mark Word恢復為無鎖狀態(tài),成功則釋放鎖,失敗則說明有競爭,升級為重量級鎖。
自適應自旋(JDK 1.6優(yōu)化)
- 自旋次數(shù)不是固定值,而是根據(jù)“前一次自旋是否成功”動態(tài)調(diào)整:
- 若前一次自旋成功,本次自旋次數(shù)增加(認為大概率再次成功);
- 若前一次自旋失敗,本次自旋次數(shù)減少(甚至直接升級為重量級鎖)。
- 優(yōu)勢:避免固定自旋次數(shù)導致的CPU浪費,適配不同競爭場景。
4.4 第四步:重量級鎖(重度競爭)
觸發(fā)條件
輕量級鎖自旋失敗,或多個線程同時競爭鎖。
核心邏輯(依賴OS互斥量,性能最差)
- 線程獲取鎖失敗后,放棄自旋,進入Monitor的
_EntryList隊列,由操作系統(tǒng)進行阻塞(從用戶態(tài)切換到內(nèi)核態(tài)); - 持有鎖的線程釋放鎖時,操作系統(tǒng)喚醒
_EntryList中的線程,重新競爭鎖; - 重量級鎖的鎖狀態(tài)為10,Mark Word存儲指向Monitor的指針。
性能瓶頸
用戶態(tài)→內(nèi)核態(tài)的切換成本極高,這也是JDK 1.5之前synchronized被詬病“重量級”的根本原因。
4.5 鎖升級流程圖解
┌─────────────┐
│ 無鎖 │
│ 標志位:01 │
│ 是否偏向:0 │
└──────┬──────┘
│ 第一個線程獲取鎖
▼
┌─────────────┐
│ 偏向鎖 │
│ 標志位:01 │
│ 是否偏向:1 │
│ 存儲:線程ID │
└──────┬──────┘
│ 另一個線程競爭
▼
┌─────────────┐
│ 輕量級鎖 │
│ 標志位:00 │
│ 存儲:鎖記錄 │←───自旋等待
└──────┬──────┘
│ 競爭加劇/自旋失敗
▼
┌─────────────┐
│ 重量級鎖 │
│ 標志位:10 │
│ 存儲:Monitor│
└─────────────┘4.6 三種鎖對比
| 鎖類型 | 優(yōu)點 | 缺點 | 適用場景 |
|---|---|---|---|
| 偏向鎖 | 加鎖解鎖無額外開銷(僅檢查) | 如果有競爭,撤銷偏向鎖有代價 | 單線程訪問同步塊 |
| 輕量級鎖 | 用CAS代替互斥量,性能好 | 自旋會占用CPU | 線程交替執(zhí)行同步塊 |
| 重量級鎖 | 線程阻塞不占用CPU | 上下文切換開銷大 | 多線程同時競爭 |

五、優(yōu)化篇:JDK 1.6的其他鎖優(yōu)化
除了鎖升級,JDK 1.6還引入了鎖消除、鎖粗化等優(yōu)化,進一步提升synchronized的性能。
5.1 鎖消除(Lock Elimination)
核心邏輯
JVM的即時編譯器(JIT)檢測到某些鎖對象是“局部變量”,且不會被多線程訪問,直接消除鎖。
示例
public String concat(String a, String b) {
// StringBuffer的append方法是同步的,但sb是局部變量,無多線程訪問
StringBuffer sb = new StringBuffer();
sb.append(a).append(b);
return sb.toString();
}
JIT編譯時會消除StringBuffer的同步鎖,等價于:
public String concat(String a, String b) {
StringBuilder sb = new StringBuilder(); // 非同步
sb.append(a).append(b);
return sb.toString();
}
5.2 鎖粗化(Lock Coarsening)
核心邏輯
將多個連續(xù)的細粒度鎖合并為一個粗粒度鎖,減少鎖的獲取/釋放次數(shù)。
示例
public void loopAdd(String str) {
StringBuffer sb = new StringBuffer();
for (int i = 0; i < 1000; i++) {
sb.append(str); // 每次append都加鎖/解鎖
}
}
JIT編譯時會將鎖粗化,等價于:
public void loopAdd(String str) {
StringBuffer sb = new StringBuffer();
synchronized (sb) { // 一次加鎖,覆蓋整個循環(huán)
for (int i = 0; i < 1000; i++) {
sb.append(str);
}
}
}

5.3 偏向鎖的啟用/關(guān)閉(JVM參數(shù))
| 參數(shù) | 作用 |
|---|---|
-XX:+UseBiasedLocking | 開啟偏向鎖(默認開啟) |
-XX:BiasedLockingStartupDelay=0 | 關(guān)閉偏向鎖4秒啟動延遲 |
-XX:-UseBiasedLocking | 關(guān)閉偏向鎖 |
?? 實戰(zhàn)建議:高并發(fā)場景下(如秒殺、高頻讀寫),偏向鎖會頻繁觸發(fā)撤銷/升級,建議關(guān)閉(
-XX:-UseBiasedLocking),直接使用輕量級鎖。
六、深度篇:synchronized的可重入性原理
synchronized是可重入鎖(同一線程可多次獲取同一把鎖),核心依賴Monitor的_count計數(shù)器:

示例代碼
public class SyncReentrant {
public synchronized void method1() {
System.out.println("method1");
method2(); // 同一線程再次獲取鎖,可重入
}
public synchronized void method2() {
System.out.println("method2");
}
public static void main(String[] args) {
new SyncReentrant().method1();
}
}可重入流程
- 線程調(diào)用
method1(),獲取鎖,Monitor的_count=1; - 線程調(diào)用
method2(),再次獲取同一把鎖,_count=2; method2()執(zhí)行完,釋放鎖,_count=1;method1()執(zhí)行完,釋放鎖,_count=0,完全釋放鎖。
核心價值
避免同一線程多次獲取同一把鎖時出現(xiàn)死鎖(如遞歸調(diào)用同步方法)。
七、面試高頻真題(標準答案直接背)
7.1 基礎(chǔ)必答
Q1:synchronized的三種使用方式及區(qū)別?
答案:
- 修飾實例方法:鎖對象是
this(當前實例),不同實例鎖互不干擾; - 修飾靜態(tài)方法:鎖對象是類的Class對象,所有實例共享同一把鎖;
- 修飾代碼塊:自定義鎖對象(如
final Object),可縮小鎖范圍,提升性能。
核心區(qū)別是鎖對象不同,導致鎖的粒度和作用域不同。
Q2:synchronized底層如何實現(xiàn)?
答案:
- 字節(jié)碼層面:代碼塊生成
monitorenter/monitorexit指令,方法修飾ACC_SYNCHRONIZED標記; - 底層依賴對象監(jiān)視器(Monitor):每個對象關(guān)聯(lián)一個Monitor,包含Owner、EntryList、WaitSet、計數(shù)器;
- JDK 1.6+引入鎖升級機制:無鎖→偏向鎖→輕量級鎖→重量級鎖,僅重度競爭時使用重量級鎖(OS互斥量),大幅提升性能。
Q3:synchronized的鎖升級過程?
答案:
- 無鎖:對象創(chuàng)建時的初始狀態(tài),Mark Word存儲哈希值;
- 偏向鎖:第一個線程獲取鎖,Mark Word寫入線程ID,后續(xù)該線程無需CAS直接獲取鎖;
- 輕量級鎖:有線程競爭偏向鎖,通過CAS自旋獲取鎖,自適應自旋失敗后升級;
- 重量級鎖:輕量級鎖自旋失敗,線程進入Monitor的EntryList阻塞,依賴OS互斥量實現(xiàn)。
鎖升級是單向不可逆的,僅能從低級別向高級別升級。

7.2 深度追問
Q4:偏向鎖、輕量級鎖、重量級鎖的性能對比?
答案:
- 偏向鎖:性能最優(yōu),無CAS、無OS調(diào)用,僅檢查線程ID;
- 輕量級鎖:性能次之,CAS自旋(用戶態(tài)),無OS阻塞;
- 重量級鎖:性能最差,用戶態(tài)→內(nèi)核態(tài)切換,線程阻塞/喚醒成本高。
核心原則:盡量讓鎖停留在偏向鎖/輕量級鎖階段,避免升級為重量級鎖。
Q5:為什么偏向鎖默認有4秒延遲?
答案:
JVM啟動時會加載大量類,多線程競爭激烈,偏向鎖會頻繁撤銷/升級,反而降低性能。4秒延遲是為了等JVM啟動完成后,再開啟偏向鎖,適配低競爭場景。
Q6:synchronized和volatile的區(qū)別?
答案:
| 維度 | synchronized | volatile |
|---|---|---|
| 原子性 | 支持(保證代碼塊原子執(zhí)行) | 不支持(僅保證可見性) |
| 可見性 | 支持(釋放鎖時刷新內(nèi)存) | 支持(禁止指令重排+內(nèi)存刷新) |
| 有序性 | 支持(通過鎖保證執(zhí)行順序) | 支持(禁止指令重排) |
| 鎖特性 | 可重入鎖,有鎖升級機制 | 無鎖,僅修飾變量 |
| 適用場景 | 代碼塊/方法的線程安全 | 變量的可見性(如狀態(tài)標記) |
Q7:高并發(fā)場景下,偏向鎖是否需要關(guān)閉?為什么?
答案:
需要關(guān)閉。原因:
- 高并發(fā)場景下,偏向鎖的線程ID會頻繁被競爭線程修改,觸發(fā)偏向鎖撤銷;
- 頻繁的撤銷/升級會產(chǎn)生額外開銷,反而比直接使用輕量級鎖更慢;
- 關(guān)閉偏向鎖后,直接進入輕量級鎖階段,避免撤銷開銷。
Q8:synchronized的可重入性原理是什么?
答案:
依賴Monitor的計數(shù)器機制:
- 線程首次獲取鎖,Monitor的計數(shù)器
_count=1; - 同一線程再次獲取鎖,計數(shù)器+1;
- 線程釋放鎖,計數(shù)器-1;
- 計數(shù)器為0時,完全釋放鎖,其他線程可競爭。
該機制避免了同一線程多次獲取同一把鎖導致的死鎖。
7.3 實戰(zhàn)場景題
Q9:如何優(yōu)化synchronized的性能?
答案:
- 縮小鎖范圍:使用代碼塊鎖替代方法鎖,僅鎖定核心臨界區(qū);
- 降低鎖粒度:如ConcurrentHashMap的分段鎖(JDK 1.7)、LongAdder的分段累加;
- 關(guān)閉偏向鎖:高并發(fā)場景下通過JVM參數(shù)關(guān)閉偏向鎖;
- 避免鎖競爭:如讀寫分離、使用無鎖數(shù)據(jù)結(jié)構(gòu)(Atomic系列);
- 利用JDK優(yōu)化:依賴JIT的鎖消除、鎖粗化。
Q10:synchronized和ReentrantLock的區(qū)別?
答案:
| 維度 | synchronized | ReentrantLock |
|---|---|---|
| 底層實現(xiàn) | JVM層面(Monitor) | JDK層面(AQS) |
| 鎖類型 | 非公平鎖(默認) | 可公平/非公平鎖 |
| 解鎖方式 | 自動解鎖(monitorexit) | 手動解鎖(必須finally中釋放) |
| 功能擴展 | 無(僅基礎(chǔ)鎖功能) | 支持中斷、超時獲取鎖、條件變量 |
| 性能 | JDK 1.6+優(yōu)化后,低競爭接近ReentrantLock | 高競爭場景性能更優(yōu) |
| 可重入性 | 支持 | 支持 |

八、常見誤區(qū)與避坑指南
8.1 典型誤區(qū)
? 誤區(qū)1:synchronized是重量級鎖,性能差
糾正:JDK 1.6+引入鎖升級后,低競爭場景下synchronized的性能接近CAS,僅重度競爭時才會升級為重量級鎖。
? 誤區(qū)2:鎖升級是可逆的
糾正:鎖升級是單向不可逆的,一旦升級為重量級鎖,不會降級為輕量級鎖/偏向鎖。
? 誤區(qū)3:偏向鎖一定提升性能
糾正:高并發(fā)場景下,偏向鎖的撤銷開銷會抵消其優(yōu)勢,建議關(guān)閉。
? 誤區(qū)4:synchronized能保證原子性,所以無需考慮可見性
糾正:synchronized同時保證原子性、可見性、有序性——釋放鎖時會將變量刷新到主內(nèi)存,獲取鎖時會從主內(nèi)存加載最新值。
8.2 編碼規(guī)范
- 縮小鎖范圍:僅鎖定臨界區(qū)代碼,避免整個方法加鎖;
- 選擇合適的鎖對象:使用
final Object作為鎖對象,避免鎖字符串常量/局部變量; - 避免鎖嵌套:減少死鎖風險,如必須嵌套,保證鎖的獲取順序一致;
- 高并發(fā)關(guān)閉偏向鎖:通過
-XX:-UseBiasedLocking關(guān)閉,提升性能; - 結(jié)合其他并發(fā)工具:如讀寫分離場景使用
ReentrantReadWriteLock,替代synchronized。
總結(jié)
1. 核心知識點速記口訣
sync有三種,實例靜態(tài)塊, 對象頭Mark,鎖狀態(tài)存, 無鎖到偏向,輕量到重量, 升級不可逆,性能逐下降, JDK1.6優(yōu)化,自旋鎖消除, 可重入計數(shù),解鎖要記清。
2. 核心要點回顧
synchronized有三種使用方式,核心區(qū)別是鎖對象不同(實例/Class/自定義對象);- 底層依賴Monitor實現(xiàn),JDK 1.6引入鎖升級機制(無鎖→偏向鎖→輕量級鎖→重量級鎖),大幅提升性能;
- 偏向鎖消除CAS開銷,輕量級鎖依賴自旋CAS,重量級鎖依賴OS互斥量;
- JDK 1.6的其他優(yōu)化:自適應自旋、鎖消除、鎖粗化;
synchronized是可重入鎖,依賴Monitor計數(shù)器實現(xiàn),同時保證原子性、可見性、有序性。
3. 實戰(zhàn)建議
- 低競爭場景:依賴默認的偏向鎖,無需額外優(yōu)化;
- 中競爭場景:縮小鎖范圍,利用輕量級鎖的自旋優(yōu)勢;
- 高競爭場景:關(guān)閉偏向鎖,結(jié)合分段鎖/無鎖結(jié)構(gòu)(如Atomic系列),避免升級為重量級鎖。

寫在最后
synchronized是Java并發(fā)的基礎(chǔ),也是面試中區(qū)分“基礎(chǔ)開發(fā)者”和“資深開發(fā)者”的關(guān)鍵。很多開發(fā)者只會用synchronized保證線程安全,但說不清楚鎖升級、Mark Word、Monitor等底層原理,最終在面試中失利。
希望這篇文章能幫你吃透synchronized的核心邏輯,不僅能背出面試答案,更能理解底層原理,在實際開發(fā)中寫出高性能、無坑的并發(fā)代碼。
到此這篇關(guān)于Java synchronized從使用到底層鎖升級機制詳解的文章就介紹到這了,更多相關(guān)Java synchronized底層鎖內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java程序的初始化順序,static{}靜態(tài)代碼塊和實例語句塊的使用方式
這篇文章主要介紹了Java程序的初始化順序,static{}靜態(tài)代碼塊和實例語句塊的使用方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-01-01
Javabean轉(zhuǎn)換成json字符并首字母大寫代碼實例
這篇文章主要介紹了javabean轉(zhuǎn)成json字符并首字母大寫代碼實例,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2020-02-02
java 利用HttpClient PostMethod提交json數(shù)據(jù)操作
這篇文章主要介紹了java 利用HttpClient PostMethod提交json數(shù)據(jù)操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-01-01
java int類型二維數(shù)組實現(xiàn)“楊輝三角”的完整實例
這篇文章主要給大家介紹了關(guān)于java int類型二維數(shù)組實現(xiàn)“楊輝三角”的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-12-12

