Java synchronized從使用到底層鎖升級(jí)機(jī)制詳解

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

1.1 修飾實(shí)例方法(實(shí)例鎖)
鎖對(duì)象是當(dāng)前實(shí)例(this),不同實(shí)例之間的鎖互不干擾。
public class SyncInstanceMethod {
// 實(shí)例鎖:鎖對(duì)象是this
public synchronized void doSomething() {
System.out.println("實(shí)例方法鎖:" + Thread.currentThread().getName());
try {
Thread.sleep(1000); // 模擬耗時(shí)操作
} 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)→ 不會(huì)阻塞
new Thread(() -> instance2.doSomething(), "線程2").start();
}
}結(jié)果:線程1和線程2同時(shí)執(zhí)行(不同實(shí)例,鎖不互斥)。
1.2 修飾靜態(tài)方法(類鎖)
鎖對(duì)象是類的Class對(duì)象(如SyncStaticMethod.class),所有實(shí)例共享同一把鎖。

public class SyncStaticMethod {
// 類鎖:鎖對(duì)象是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對(duì)象)
new Thread(() -> instance1.doStaticSomething(), "線程1").start();
// 線程2:調(diào)用靜態(tài)方法(同一把類鎖)→ 阻塞
new Thread(() -> instance2.doStaticSomething(), "線程2").start();
}
}結(jié)果:線程2等待線程1執(zhí)行完后才執(zhí)行(類鎖全局唯一)。
1.3 修飾代碼塊(自定義鎖對(duì)象)
靈活指定鎖對(duì)象,是最常用的方式(縮小鎖范圍,提升性能)。

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

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

2.1 字節(jié)碼層面:monitorenter/monitorexit指令
編譯后的synchronized代碼塊會(huì)生成monitorenter和monitorexit指令,修飾方法則會(huì)在方法表中標(biāo)記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) 對(duì)應(yīng)的字節(jié)碼 0 aload_0 1 getfield #2 // 獲取lock對(duì)象 4 dup 5 astore_1 6 monitorenter // 進(jìn)入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 // 異常時(shí)的monitorexit(保證鎖釋放) 23 aload_2 24 athrow 25 return
指令解析
monitorenter:嘗試獲取對(duì)象的Monitor所有權(quán),成功則計(jì)數(shù)器+1,失敗則線程阻塞;monitorexit:釋放Monitor所有權(quán),計(jì)數(shù)器-1,當(dāng)計(jì)數(shù)器為0時(shí),完全釋放鎖;- 編譯器會(huì)生成兩個(gè)
monitorexit:一個(gè)正常退出,一個(gè)異常退出(保證鎖最終釋放,避免死鎖)。
2.2 Monitor(對(duì)象監(jiān)視器):synchronized的核心依賴
每個(gè)Java對(duì)象都關(guān)聯(lián)一個(gè)Monitor(C++實(shí)現(xiàn),ObjectMonitor類),其核心結(jié)構(gòu)如下:

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

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

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

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

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

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

5.3 偏向鎖的啟用/關(guān)閉(JVM參數(shù))
| 參數(shù) | 作用 |
|---|---|
-XX:+UseBiasedLocking | 開啟偏向鎖(默認(rèn)開啟) |
-XX:BiasedLockingStartupDelay=0 | 關(guān)閉偏向鎖4秒啟動(dòng)延遲 |
-XX:-UseBiasedLocking | 關(guān)閉偏向鎖 |
?? 實(shí)戰(zhàn)建議:高并發(fā)場(chǎng)景下(如秒殺、高頻讀寫),偏向鎖會(huì)頻繁觸發(fā)撤銷/升級(jí),建議關(guān)閉(
-XX:-UseBiasedLocking),直接使用輕量級(jí)鎖。
六、深度篇:synchronized的可重入性原理
synchronized是可重入鎖(同一線程可多次獲取同一把鎖),核心依賴Monitor的_count計(jì)數(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,完全釋放鎖。
核心價(jià)值
避免同一線程多次獲取同一把鎖時(shí)出現(xiàn)死鎖(如遞歸調(diào)用同步方法)。
七、面試高頻真題(標(biāo)準(zhǔn)答案直接背)
7.1 基礎(chǔ)必答
Q1:synchronized的三種使用方式及區(qū)別?
答案:
- 修飾實(shí)例方法:鎖對(duì)象是
this(當(dāng)前實(shí)例),不同實(shí)例鎖互不干擾; - 修飾靜態(tài)方法:鎖對(duì)象是類的Class對(duì)象,所有實(shí)例共享同一把鎖;
- 修飾代碼塊:自定義鎖對(duì)象(如
final Object),可縮小鎖范圍,提升性能。
核心區(qū)別是鎖對(duì)象不同,導(dǎo)致鎖的粒度和作用域不同。
Q2:synchronized底層如何實(shí)現(xiàn)?
答案:
- 字節(jié)碼層面:代碼塊生成
monitorenter/monitorexit指令,方法修飾ACC_SYNCHRONIZED標(biāo)記; - 底層依賴對(duì)象監(jiān)視器(Monitor):每個(gè)對(duì)象關(guān)聯(lián)一個(gè)Monitor,包含Owner、EntryList、WaitSet、計(jì)數(shù)器;
- JDK 1.6+引入鎖升級(jí)機(jī)制:無鎖→偏向鎖→輕量級(jí)鎖→重量級(jí)鎖,僅重度競爭時(shí)使用重量級(jí)鎖(OS互斥量),大幅提升性能。
Q3:synchronized的鎖升級(jí)過程?
答案:
- 無鎖:對(duì)象創(chuàng)建時(shí)的初始狀態(tài),Mark Word存儲(chǔ)哈希值;
- 偏向鎖:第一個(gè)線程獲取鎖,Mark Word寫入線程ID,后續(xù)該線程無需CAS直接獲取鎖;
- 輕量級(jí)鎖:有線程競爭偏向鎖,通過CAS自旋獲取鎖,自適應(yīng)自旋失敗后升級(jí);
- 重量級(jí)鎖:輕量級(jí)鎖自旋失敗,線程進(jìn)入Monitor的EntryList阻塞,依賴OS互斥量實(shí)現(xiàn)。
鎖升級(jí)是單向不可逆的,僅能從低級(jí)別向高級(jí)別升級(jí)。

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

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

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

