深入理解Java中的synchronized 和 ReentrantLock 底層原理
synchronized 和 ReentrantLock 是 Java 中最核心的兩種鎖實現(xiàn),前者是 JVM 內(nèi)置鎖(隱式鎖),后者是 JUC 顯式鎖(基于 AQS 框架)。兩者的實現(xiàn)底層依賴完全不同,但核心目標(biāo)一致 —— 通過「互斥」保證并發(fā)安全,下面從 底層依賴、核心流程、關(guān)鍵機(jī)制 三個維度,深入拆解它們的實現(xiàn)原理:
一、synchronized的實現(xiàn):JVM 層面的 “自動鎖”
synchronized 是 Java 語言原生支持的鎖,無需手動釋放,底層由 JVM 解釋器 + 操作系統(tǒng)內(nèi)核 協(xié)同實現(xiàn),核心依賴 對象頭(Mark Word) 和 監(jiān)視器(Monitor),并通過「鎖升級」機(jī)制優(yōu)化性能。
1. 核心依賴組件
(1)對象頭(Mark Word):鎖狀態(tài)的 “存儲載體”
Java 中所有對象都能作為鎖,本質(zhì)是因為對象在內(nèi)存中的「對象頭」包含了鎖相關(guān)的狀態(tài)信息。對象頭由兩部分組成:Mark Word(核心)和 Klass Pointer(指向類元數(shù)據(jù))。
Mark Word 是一個動態(tài)結(jié)構(gòu)(32 位 JVM 示例),會根據(jù)鎖狀態(tài)變化存儲不同信息,核心字段包括:
| 鎖狀態(tài) | Mark Word 結(jié)構(gòu)(32 位) | 核心作用 |
|---|---|---|
| 無鎖狀態(tài) | 哈希碼(25 位) + 分代年齡(4 位) + 鎖標(biāo)志位(01) | 存儲對象哈希碼、GC 分代信息,無鎖標(biāo)識 |
| 偏向鎖 | 線程 ID(23 位) + Epoch(2 位) + 分代年齡(4 位) + 鎖標(biāo)志位(01) | 記錄持有鎖的線程 ID,實現(xiàn) “無競爭時零開銷” |
| 輕量級鎖 | 指向棧幀中 “鎖記錄” 的指針(30 位) + 鎖標(biāo)志位(00) | 關(guān)聯(lián)線程棧中的鎖記錄,實現(xiàn)自旋鎖機(jī)制 |
| 重量級鎖 | 指向 Monitor 的指針(30 位) + 鎖標(biāo)志位(10) | 關(guān)聯(lián)操作系統(tǒng)級別的 Monitor,實現(xiàn)阻塞同步 |
?? 關(guān)鍵:Mark Word 的「鎖標(biāo)志位」決定當(dāng)前鎖狀態(tài),JVM 通過修改 Mark Word 實現(xiàn)鎖的切換。
(2)監(jiān)視器(Monitor):重量級鎖的 “核心同步工具”
Monitor 是操作系統(tǒng)層面的「互斥同步原語」(也叫管程),是 synchronized 重量級鎖的核心依賴。每個 Java 對象在創(chuàng)建時,JVM 會為其關(guān)聯(lián)一個 Monitor(可理解為 “鎖對象”),Monitor 內(nèi)部包含三個核心結(jié)構(gòu):
- 所有者(Owner):存儲當(dāng)前持有鎖的線程(唯一,保證互斥);
- EntryList(阻塞隊列):存儲等待獲取鎖的線程(競爭失敗后進(jìn)入阻塞狀態(tài));
- WaitSet(等待隊列):存儲調(diào)用
wait()方法后釋放鎖的線程(需被notify()喚醒)。
?? 本質(zhì):Monitor 通過操作系統(tǒng)的「互斥量(Mutex)」實現(xiàn)線程阻塞 / 喚醒,是重量級鎖性能開銷的主要來源。
2.synchronized的核心實現(xiàn)流程(鎖升級)
synchronized 并非一開始就是重量級鎖,JVM 會根據(jù)「競爭激烈程度」自動觸發(fā)「鎖升級」(無鎖→偏向鎖→輕量級鎖→重量級鎖),目的是在 “無競爭” 和 “激烈競爭” 場景下都能保證性能。
(1)無鎖 → 偏向鎖(單線程競爭)
- 觸發(fā)條件:只有一個線程嘗試獲取鎖。
- 實現(xiàn)流程:
- 線程 T1 第一次進(jìn)入同步塊時,JVM 通過 CAS 操作 修改對象的
Mark Word:將「線程 ID」寫入Mark Word,鎖標(biāo)志位保持 01(偏向鎖標(biāo)識); - 后續(xù) T1 再次進(jìn)入同步塊時,只需判斷
Mark Word中的線程 ID 是否為自己:- 是:直接進(jìn)入(無需任何同步操作,零開銷);
- 否:偏向鎖失效,觸發(fā)升級。
- 線程 T1 第一次進(jìn)入同步塊時,JVM 通過 CAS 操作 修改對象的
- 核心目的:優(yōu)化 “單線程重復(fù)獲取鎖” 的場景(如單線程操作集合),避免無意義的同步開銷。
(2)偏向鎖 → 輕量級鎖(低并發(fā)交替競爭)
- 觸發(fā)條件:有第二個線程 T2 嘗試獲取同一把鎖(T1 未釋放)。
- 實現(xiàn)流程:
- JVM 先暫停 T1,撤銷偏向鎖(將
Mark Word恢復(fù)為無鎖狀態(tài)); - T1 和 T2 各自在自己的「棧幀」中創(chuàng)建一個「鎖記錄(Lock Record)」,鎖記錄中存儲對象
Mark Word的副本(Displaced Mark Word); - 兩個線程通過 CAS 操作 競爭修改對象的
Mark Word:將Mark Word指向自己的鎖記錄;- 成功:獲取輕量級鎖,進(jìn)入同步塊;
- 失?。赫f明競爭存在,進(jìn)入自旋重試(循環(huán)嘗試 CAS,避免阻塞)。
- JVM 先暫停 T1,撤銷偏向鎖(將
- 核心目的:優(yōu)化 “多線程交替執(zhí)行” 的場景(無激烈競爭),通過自旋減少線程阻塞 / 喚醒的開銷(用戶態(tài)操作,比內(nèi)核態(tài)高效)。
(3)輕量級鎖 → 重量級鎖(高并發(fā)激烈競爭)
- 觸發(fā)條件:自旋次數(shù)超過閾值(JDK1.6 后為「自適應(yīng)自旋」,根據(jù)前一次自旋成功率動態(tài)調(diào)整),或有更多線程參與競爭。
- 實現(xiàn)流程:
- 自旋失敗的線程放棄競爭,將鎖升級為重量級鎖(修改
Mark Word指向 Monitor); - 線程進(jìn)入 Monitor 的
EntryList隊列,放棄 CPU 資源(進(jìn)入 阻塞狀態(tài),由操作系統(tǒng)調(diào)度); - 持有鎖的線程釋放鎖時,通過操作系統(tǒng)喚醒
EntryList中的一個線程,該線程再次嘗試獲取鎖。
- 自旋失敗的線程放棄競爭,將鎖升級為重量級鎖(修改
- 核心目的:應(yīng)對 “高并發(fā)激烈競爭” 場景,通過阻塞機(jī)制避免 CPU 空轉(zhuǎn)(自旋會消耗 CPU)。
3. 解鎖與自動釋放機(jī)制
synchronized 無需手動解鎖,JVM 會在以下場景自動釋放鎖:
- 線程退出同步塊(
synchronized代碼塊或方法); - 線程拋出異常時(JVM 會在異常處理中插入解鎖指令)。
解鎖的核心操作:
- 輕量級鎖:通過 CAS 將
Mark Word恢復(fù)為「Displaced Mark Word」(鎖記錄中的副本); - 重量級鎖:將 Monitor 的「Owner」設(shè)為 null,喚醒
EntryList中的線程。
4. 內(nèi)存語義的實現(xiàn)
synchronized 除了互斥,還能保證可見性和有序性,底層通過 內(nèi)存屏障(Memory Barrier) 實現(xiàn):
- 進(jìn)入同步塊時:插入「Load Barrier」,清空工作內(nèi)存,從主內(nèi)存加載最新共享變量(保證可見性);
- 退出同步塊時:插入「Store Barrier」,將工作內(nèi)存中的修改刷新到主內(nèi)存(保證可見性);
- 同步塊內(nèi):禁止指令重排序(保證有序性)。
二、ReentrantLock 的實現(xiàn):JUC 層面的 “手動鎖”
ReentrantLock 是 java.util.concurrent.locks 包下的顯式鎖,基于 AQS(AbstractQueuedSynchronizer,抽象隊列同步器) 實現(xiàn),支持公平鎖 / 非公平鎖、可中斷、條件鎖等靈活特性,完全由 Java 代碼實現(xiàn)(無需 JVM 介入)。
1. 核心依賴組件:AQS(鎖的 “骨架”)
AQS 是 JUC 同步工具的基礎(chǔ)(如 ReentrantLock、Semaphore、CountDownLatch),其核心設(shè)計是「狀態(tài)變量 + 同步隊列」,所有鎖的邏輯都圍繞 AQS 擴(kuò)展。
(1)AQS 的核心結(jié)構(gòu)
- 狀態(tài)變量(state):
volatile int state,存儲鎖的狀態(tài):state = 0:鎖未被持有;state > 0:鎖已被持有,值等于「重入次數(shù)」(支持可重入);
- 同步隊列(CLH 隊列):雙向鏈表,每個節(jié)點(diǎn)是
Node對象,存儲等待鎖的線程、等待狀態(tài)(如CANCELLED、WAITING)等; - 獨(dú)占線程(exclusiveOwnerThread):存儲當(dāng)前持有鎖的線程(僅用于獨(dú)占鎖,
ReentrantLock是獨(dú)占鎖)。
(2)AQS 的核心模板方法
AQS 定義了一套「模板方法」,子類(如 ReentrantLock 的內(nèi)部類 Sync)只需實現(xiàn)以下抽象方法,即可完成鎖的邏輯:
tryAcquire(int arg):嘗試獲取鎖(arg 為獲取鎖的次數(shù),重入時 arg=1);tryRelease(int arg):嘗試釋放鎖(arg 為釋放鎖的次數(shù),重入時 arg=1);isHeldExclusively():判斷當(dāng)前線程是否持有鎖(支持可重入)。
?? 本質(zhì):AQS 封裝了 “隊列管理”“線程阻塞 / 喚醒” 等通用邏輯,子類只需實現(xiàn) “鎖的獲取 / 釋放” 核心邏輯,遵循「模板方法設(shè)計模式」。
2. ReentrantLock 的內(nèi)部結(jié)構(gòu)
ReentrantLock 本身不直接實現(xiàn) AQS,而是通過內(nèi)部類 Sync 繼承 AQS,再由 Sync 的兩個子類實現(xiàn)「公平鎖」和「非公平鎖」:
public class ReentrantLock implements Lock {
// 核心同步器(繼承 AQS)
private final Sync sync;
// 抽象內(nèi)部類:繼承 AQS,定義鎖的基礎(chǔ)邏輯
abstract static class Sync extends AbstractQueuedSynchronizer {
abstract void lock(); // 抽象方法,由公平/非公平鎖實現(xiàn)
// ... 實現(xiàn) tryRelease 等通用方法
}
// 非公平鎖實現(xiàn)(默認(rèn))
static final class NonfairSync extends Sync {
@Override void lock() { /* 非公平鎖加鎖邏輯 */ }
@Override protected boolean tryAcquire(int arg) { /* 非公平嘗試獲取鎖 */ }
}
// 公平鎖實現(xiàn)
static final class FairSync extends Sync {
@Override void lock() { /* 公平鎖加鎖邏輯 */ }
@Override protected boolean tryAcquire(int arg) { /* 公平嘗試獲取鎖 */ }
}
// 構(gòu)造器:默認(rèn)非公平鎖
public ReentrantLock() { sync = new NonfairSync(); }
// 構(gòu)造器:指定公平/非公平
public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }
}3. ReentrantLock 的核心實現(xiàn)流程(以非公平鎖為例)
(1)加鎖流程(lock() 方法)
// NonfairSync 的 lock() 方法
@Override
public void lock() {
// 第一步:嘗試 CAS 搶鎖(state 從 0 改為 1)
if (compareAndSetState(0, 1)) {
// 搶鎖成功,設(shè)置當(dāng)前線程為獨(dú)占線程
setExclusiveOwnerThread(Thread.currentThread());
} else {
// 搶鎖失敗,調(diào)用 AQS 的 acquire() 模板方法
acquire(1);
}
}
// AQS 的 acquire() 模板方法(通用邏輯)
public final void acquire(int arg) {
// 第二步:嘗試獲取鎖(tryAcquire 由 NonfairSync 實現(xiàn))
if (!tryAcquire(arg) &&
// 第三步:獲取失敗,將線程封裝為 Node 加入同步隊列
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) {
// 第四步:如果需要,中斷當(dāng)前線程(支持可中斷)
selfInterrupt();
}
}
// NonfairSync 的 tryAcquire() 方法(核心搶鎖邏輯)
@Override
protected boolean tryAcquire(int arg) {
return nonfairTryAcquire(arg);
}
// Sync 的 nonfairTryAcquire() 方法(非公平搶鎖細(xì)節(jié))
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState(); // 獲取當(dāng)前鎖狀態(tài)
// 情況 1:鎖未被持有(state=0),再次嘗試 CAS 搶鎖
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 情況 2:鎖已被當(dāng)前線程持有(可重入),state 加 1
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) throw new Error("Maximum lock count exceeded");
setState(nextc); // 無需 CAS,當(dāng)前線程已持有鎖,線程安全
return true;
}
// 情況 3:鎖被其他線程持有,搶鎖失敗
return false;
}加鎖核心步驟總結(jié):
- 線程先嘗試「CAS 搶鎖」(插隊,非公平鎖的核心);
- 搶鎖成功:設(shè)置
state=1,標(biāo)記當(dāng)前線程為獨(dú)占線程; - 搶鎖失?。?ul>
- 若當(dāng)前線程已持有鎖(重入):
state += 1,直接成功; - 若鎖被其他線程持有:將線程封裝為
Node加入 AQS 同步隊列,調(diào)用LockSupport.park()阻塞線程。
(2)解鎖流程(unlock() 方法)
// ReentrantLock 的 unlock() 方法
public void unlock() {
// 調(diào)用 Sync 的 release() 方法(AQS 模板方法)
sync.release(1);
}
// AQS 的 release() 模板方法(通用邏輯)
public final boolean release(int arg) {
// 第一步:嘗試釋放鎖(tryRelease 由 Sync 實現(xiàn))
if (tryRelease(arg)) {
// 第二步:釋放成功,喚醒同步隊列中的頭節(jié)點(diǎn)線程
Node h = head;
if (h != null && h.waitStatus != 0) {
unparkSuccessor(h); // 喚醒后繼節(jié)點(diǎn)(LockSupport.unpark())
}
return true;
}
return false;
}
// Sync 的 tryRelease() 方法(核心釋放邏輯)
@Override
protected boolean tryRelease(int releases) {
int c = getState() - releases; // state 減 1(重入時多次減)
// 只有持有鎖的線程能釋放(否則拋異常)
if (Thread.currentThread() != getExclusiveOwnerThread()) {
throw new IllegalMonitorStateException();
}
boolean free = false;
// 情況 1:state 減為 0,完全釋放鎖
if (c == 0) {
free = true;
setExclusiveOwnerThread(null); // 清空獨(dú)占線程
}
// 情況 2:state > 0(重入未完全釋放),僅更新 state
setState(c);
return free;
}解鎖核心步驟總結(jié):
- 線程調(diào)用
unlock(),state減 1; - 若
state == 0:完全釋放鎖,清空獨(dú)占線程,喚醒 AQS 同步隊列中的后繼線程; - 若
state > 0:僅更新state(重入鎖未完全釋放)。
4. 公平鎖與非公平鎖的實現(xiàn)差異
核心差異在 tryAcquire() 方法中,公平鎖會多一步「判斷隊列是否有等待線程」:
// 公平鎖 FairSync 的 tryAcquire() 方法
@Override
protected boolean tryAcquire(int arg) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 關(guān)鍵差異:先判斷同步隊列是否有前驅(qū)線程(hasQueuedPredecessors())
if (!hasQueuedPredecessors() && compareAndSetState(0, arg)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
// 重入邏輯與非公平鎖一致
int nextc = c + arg;
if (nextc < 0) throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
// AQS 的 hasQueuedPredecessors() 方法:判斷隊列是否有等待線程(先到先得)
public final boolean hasQueuedPredecessors() {
Node t = tail;
Node h = head;
Node s;
// 隊列不為空,且當(dāng)前線程不是隊列的第一個等待線程 → 有前驅(qū)線程
return h != t && ((s = h.next) == null || s.thread != Thread.currentThread());
}- 非公平鎖:搶鎖時直接 CAS,不關(guān)心隊列是否有等待線程(允許插隊,性能高,但可能導(dǎo)致饑餓);
- 公平鎖:搶鎖前先檢查隊列是否有前驅(qū)線程,只有隊列空或當(dāng)前線程是第一個時才 CAS(先到先得,無饑餓,但隊列操作有性能開銷)。
5. 可中斷與條件鎖的實現(xiàn)
(1)可中斷鎖(lockInterruptibly())
ReentrantLock 支持線程在等待鎖時被中斷,底層通過 AQS 的 acquireInterruptibly() 實現(xiàn):
- 線程在同步隊列中阻塞時,若收到中斷信號(
Thread.interrupt()),會拋出InterruptedException并退出等待; synchronized不支持可中斷(線程一旦進(jìn)入阻塞,必須等到鎖釋放或程序終止)。
(2)條件鎖(Condition)
ReentrantLock 可通過 newCondition() 獲取 Condition 對象,實現(xiàn) “線程等待 / 喚醒” 的精細(xì)化控制(類似 Object.wait()/notify(),但支持多個等待隊列):
Condition的底層是 AQS 的「等待隊列」(每個Condition對應(yīng)一個獨(dú)立等待隊列);- 調(diào)用
condition.await():線程釋放鎖,進(jìn)入Condition的等待隊列,阻塞; - 調(diào)用
condition.signal():喚醒Condition等待隊列中的一個線程,使其加入 AQS 同步隊列競爭鎖。
三、synchronized與ReentrantLock實現(xiàn)對比
| 維度 | synchronized | ReentrantLock |
|---|---|---|
| 實現(xiàn)層面 | JVM 內(nèi)置(C++ 實現(xiàn),如 HotSpot 的 synchronizer.cpp) | Java 代碼層面(基于 AQS 框架,純 Java 實現(xiàn)) |
| 核心依賴 | 對象頭(Mark Word) + Monitor(操作系統(tǒng)互斥量) | AQS(狀態(tài)變量 + 同步隊列) + CAS + LockSupport |
| 鎖升級 | 自動升級(無鎖→偏向→輕量→重量) | 無鎖升級,始終是重量級鎖(但通過 CAS 減少阻塞) |
| 釋放機(jī)制 | 自動釋放(退出同步塊 / 拋異常) | 手動釋放(必須在 finally 中調(diào)用 unlock()) |
| 公平性 | 僅支持非公平鎖 | 支持公平 / 非公平(構(gòu)造器指定) |
| 可中斷性 | 不支持 | 支持(lockInterruptibly()) |
| 條件鎖 | 不支持(僅 Object.wait()/notify(),單等待隊列) | 支持(Condition,多等待隊列) |
| 性能(JDK1.6+) | 與 ReentrantLock 接近(鎖升級優(yōu)化后) | 高并發(fā)下略優(yōu)(靈活控制隊列和 CAS) |
總結(jié)
synchronized:JVM 原生支持,無需手動管理,通過「對象頭 + Monitor + 鎖升級」實現(xiàn),兼顧簡單性和性能,適合大多數(shù)基礎(chǔ)并發(fā)場景;ReentrantLock:基于 AQS 框架,通過「狀態(tài)變量 + 同步隊列 + CAS」實現(xiàn),支持公平鎖、可中斷、條件鎖等靈活特性,適合復(fù)雜并發(fā)場景(如高并發(fā)讀寫、精細(xì)化線程控制)。
兩者的核心都是「通過互斥保證原子性,通過內(nèi)存屏障保證可見性和有序性」,但實現(xiàn)層面的差異導(dǎo)致了功能和性能的區(qū)別 —— 實際開發(fā)中,簡單場景用 synchronized(不易出錯),復(fù)雜場景用 ReentrantLock(靈活可控)。
到此這篇關(guān)于深入理解Java中的synchronized 和 ReentrantLock 底層原理的文章就介紹到這了,更多相關(guān)java synchronized 和 reentrantlock內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring?Boot實現(xiàn)文件上傳的兩種方式總結(jié)
應(yīng)用開發(fā)過程中,文件上傳是一個基礎(chǔ)的擴(kuò)展功能,它的目的就是讓大家共享我們上傳的文件資源,下面這篇文章主要給大家總結(jié)介紹了關(guān)于Spring?Boot實現(xiàn)文件上傳的兩種方式,需要的朋友可以參考下2023-05-05
k8s中java如何設(shè)置jvm堆棧大小不超過request/limit
這篇文章主要介紹了k8s中java如何設(shè)置jvm堆棧大小不超過request/limit問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2025-07-07
解決IDEA使用springBoot創(chuàng)建項目,lombok標(biāo)注實體類后編譯無報錯,但是運(yùn)行時報錯問題
文章詳細(xì)描述了在使用lombok的@Data注解標(biāo)注實體類時遇到編譯無誤但運(yùn)行時報錯的問題,分析了可能的原因,并提供了解決步驟2025-01-01
java application maven項目打自定義zip包實例(推薦)
下面小編就為大家?guī)硪黄猨ava application maven項目打自定義zip包實例(推薦)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-05-05

