Java中Synchronized與Lock鎖機(jī)制的區(qū)別詳解
在 Java 并發(fā)編程中,synchronized 和 Lock 是最常用的兩種鎖機(jī)制。它們都能保證線程安全,但實(shí)現(xiàn)原理、功能特性和性能表現(xiàn)上有顯著差異。本文將帶你徹底搞懂兩者的區(qū)別,并通過流程圖和對(duì)比表格助你做出正確的技術(shù)選型。
一、引言
線程安全是并發(fā)編程的核心問題。Java 提供了多種鎖機(jī)制,其中 synchronized 是 JVM 內(nèi)置的關(guān)鍵字,而 Lock 是 java.util.concurrent.locks 包下的接口(典型實(shí)現(xiàn)如 ReentrantLock)。許多開發(fā)者只知道“Lock 更靈活”,卻不清楚具體靈活在哪里,以及什么場(chǎng)景該用哪個(gè)。
本文將圍繞以下維度展開對(duì)比:
- 語法與使用方式
- 鎖的獲取與釋放機(jī)制
- 可中斷性、超時(shí)嘗試
- 公平性選擇
- 底層實(shí)現(xiàn)(偏向鎖、輕量級(jí)鎖、重量級(jí)鎖 vs AQS)
- 性能表現(xiàn)(JDK 1.6 前后的變化)
最后給出實(shí)戰(zhàn)建議與流程圖,幫助你直觀理解兩者在鎖競(jìng)爭(zhēng)時(shí)的行為差異。
二、一句話總結(jié)核心區(qū)別(先入為主)
| 特性 | synchronized | Lock(以 ReentrantLock 為例) |
|---|---|---|
| 實(shí)現(xiàn)層級(jí) | JVM 關(guān)鍵字,內(nèi)置語言特性 | Java 類庫(kù),基于 AQS 的 API |
| 鎖的獲取方式 | 隱式自動(dòng)獲取/釋放 | 顯式調(diào)用 lock() / unlock() |
| 可中斷性 | 不可中斷(除非拋出異常) | 支持 lockInterruptibly() |
| 超時(shí)獲取鎖 | 不支持 | 支持 tryLock(timeout, unit) |
| 公平鎖 | 非公平鎖(僅) | 可公平(構(gòu)造參數(shù))也可非公平 |
| 條件變量 | 每個(gè)對(duì)象只有一個(gè)等待隊(duì)列(wait/notify) | 一個(gè) Lock 可綁定多個(gè) Condition |
| 是否可重入 | 是 | 是(ReentrantLock 可實(shí)現(xiàn)) |
| 鎖釋放方式 | 自動(dòng)(方法結(jié)束或異常) | 必須手動(dòng)在 finally 中釋放 |
| 鎖狀態(tài)監(jiān)控 | 無直接 API | 提供 tryLock()、isHeldByCurrentThread() 等 |
| 性能(低競(jìng)爭(zhēng)) | 較好(JVM 優(yōu)化,偏向鎖) | 略差(對(duì)象創(chuàng)建開銷) |
| 性能(高競(jìng)爭(zhēng)) | 早期較差,JDK 1.6 后改善 | 穩(wěn)定可控,吞吐量有時(shí)更高 |
三、深入對(duì)比:原理與代碼示例
1. 語法與使用方式
synchronized:修飾實(shí)例方法、靜態(tài)方法或代碼塊。
// 實(shí)例方法鎖(當(dāng)前實(shí)例)
public synchronized void method1() { /* 臨界區(qū) */ }
// 靜態(tài)方法鎖(Class 對(duì)象)
public static synchronized void method2() { /* 臨界區(qū) */ }
// 代碼塊鎖(自定義對(duì)象)
public void method3() {
synchronized (this) {
// 臨界區(qū)
}
}
Lock:需要顯式創(chuàng)建鎖對(duì)象,并在 finally 塊中釋放。
Lock lock = new ReentrantLock();
lock.lock();
try {
// 臨界區(qū)
} finally {
lock.unlock(); // 必須手動(dòng)釋放,否則死鎖
}
常見錯(cuò)誤:忘記在 finally 中 unlock(),導(dǎo)致鎖無法釋放。這也是 synchronized 的“自動(dòng)釋放”優(yōu)勢(shì)所在。
2. 鎖的可中斷性
synchronized線程在等待獲取鎖時(shí)不可被中斷(Thread.interrupt()無效,會(huì)一直阻塞)。Lock提供了lockInterruptibly(),允許等待鎖的線程響應(yīng)中斷。
lock.lockInterruptibly(); // 等待時(shí)可被中斷
try {
// ...
} catch (InterruptedException e) {
// 處理中斷
} finally {
lock.unlock();
}
3. 超時(shí)獲取鎖(避免死鎖)
synchronized 無法嘗試獲取鎖一段時(shí)間后放棄。Lock 的 tryLock(long time, TimeUnit unit) 能實(shí)現(xiàn)非阻塞嘗試:
if (lock.tryLock(1, TimeUnit.SECONDS)) {
try {
// 獲取成功
} finally {
lock.unlock();
}
} else {
// 未獲取到鎖,執(zhí)行其他邏輯
}
4. 公平性
synchronized只支持非公平鎖(等待隊(duì)列中隨機(jī)或按操作系統(tǒng)調(diào)度,可能線程餓死)。ReentrantLock可選公平鎖:new ReentrantLock(true)。公平鎖保證等待時(shí)間最長(zhǎng)的線程先獲得鎖,但會(huì)降低吞吐量。
Lock fairLock = new ReentrantLock(true); // 公平鎖 Lock unfairLock = new ReentrantLock(false); // 非公平鎖(默認(rèn))
5. 條件變量(Condition)
synchronized 通過 wait()、notify() / notifyAll() 實(shí)現(xiàn)等待/通知,每個(gè)對(duì)象只有一個(gè)等待集。Lock 可以創(chuàng)建多個(gè) Condition 對(duì)象,實(shí)現(xiàn)更精細(xì)的線程喚醒。
ReentrantLock lock = new ReentrantLock();
Condition notFull = lock.newCondition();
Condition notEmpty = lock.newCondition();
// 生產(chǎn)者
lock.lock();
try {
while (隊(duì)列滿) notFull.await();
// 生產(chǎn)
notEmpty.signal();
} finally { lock.unlock(); }
四、底層實(shí)現(xiàn)原理簡(jiǎn)述
synchronized 的升級(jí)過程(JDK 1.6 優(yōu)化)
在 JDK 1.6 之前,synchronized 是重量級(jí)鎖(依賴操作系統(tǒng)的 mutex,用戶態(tài)到內(nèi)核態(tài)切換代價(jià)大)。1.6 之后引入了偏向鎖 → 輕量級(jí)鎖 → 重量級(jí)鎖的升級(jí)過程:
- 偏向鎖:鎖總是被同一個(gè)線程獲取,無需 CAS。
- 輕量級(jí)鎖:多線程交替執(zhí)行,使用 CAS 嘗試獲取鎖,不阻塞。
- 重量級(jí)鎖:競(jìng)爭(zhēng)激烈,阻塞線程。

Lock(ReentrantLock)的 AQS 原理
ReentrantLock 基于 AQS(AbstractQueuedSynchronizer),內(nèi)部維護(hù)一個(gè) FIFO 等待隊(duì)列和一個(gè) volatile int state 表示鎖狀態(tài)。通過 CAS 設(shè)置狀態(tài),失敗則加入等待隊(duì)列并阻塞(LockSupport.park())。
兩者底層差異決定:
synchronized的鎖釋放由 JVM 保證(異?;蚍椒ńY(jié)束),不會(huì)遺漏。Lock的靈活性更高,但手動(dòng)釋放容易出錯(cuò)。
五、流程圖對(duì)比:鎖獲取流程
synchronized 獲取鎖流程

Lock(ReentrantLock)獲取鎖流程

六、性能測(cè)試與選型建議
性能歷史
- JDK 1.5:
synchronized性能很差,ReentrantLock明顯更優(yōu)。 - JDK 1.6+:
synchronized經(jīng)過優(yōu)化(偏向鎖、輕量級(jí)鎖、適應(yīng)性自旋),低競(jìng)爭(zhēng)場(chǎng)景下性能與Lock相當(dāng)甚至略好。 - 高競(jìng)爭(zhēng)場(chǎng)景:兩者性能差別不大,但
Lock提供了更多控制(如公平鎖、可中斷)。
選擇指南
| 場(chǎng)景 | 推薦鎖 | 原因 |
|---|---|---|
| 簡(jiǎn)單同步代碼塊,無需高級(jí)特性 | synchronized | 簡(jiǎn)潔、安全、自動(dòng)釋放 |
| 需要嘗試獲取鎖并超時(shí)退出 | Lock | tryLock 超時(shí)機(jī)制 |
| 需要可中斷的鎖獲取 | Lock | lockInterruptibly |
| 需要多個(gè)條件變量(生產(chǎn)者-消費(fèi)者) | Lock | Condition 更靈活 |
| 需要公平鎖 | Lock | synchronized 不支持 |
| 性能要求極高且競(jìng)爭(zhēng)極低 | synchronized | 偏向鎖開銷小 |
| 鎖粗粒度,手動(dòng)控制釋放時(shí)機(jī) | Lock | 可在不同方法間釋放 |
七、實(shí)戰(zhàn)代碼對(duì)比:模擬售票系統(tǒng)
以下示例展示兩種方式實(shí)現(xiàn)線程安全的余票扣除。
使用 synchronized
class TicketSync {
private int tickets = 100;
public synchronized boolean sell() {
if (tickets <= 0) return false;
tickets--;
return true;
}
}
使用 ReentrantLock
class TicketLock {
private int tickets = 100;
private final Lock lock = new ReentrantLock();
public boolean sell() {
lock.lock();
try {
if (tickets <= 0) return false;
tickets--;
return true;
} finally {
lock.unlock();
}
}
}
八、常見誤區(qū)
1.“Lock 一定比 synchronized 快”
錯(cuò)。JDK 1.6 后,低競(jìng)爭(zhēng)下 synchronized 有偏向鎖優(yōu)化,性能不差;高競(jìng)爭(zhēng)下兩者幾乎持平。
2.“synchronized 不可重入”
錯(cuò)。synchronized 是可重入鎖,同一個(gè)線程可以多次進(jìn)入。
3.“Lock 必須要 try-finally,否則死鎖”
對(duì)。若臨界區(qū)拋出異常未釋放鎖,其他線程將永久等待。synchronized 由 JVM 保證釋放。
4.“公平鎖一定比非公平鎖好”
錯(cuò)。公平鎖減少了線程餓死,但上下文切換更頻繁,吞吐量通常更低。
九、總結(jié)與思維導(dǎo)圖

最終建議:
- 優(yōu)先使用
synchronized,除非遇到必須使用Lock的場(chǎng)景(如超時(shí)、中斷、多條件)。 - 永遠(yuǎn)不要自己實(shí)現(xiàn)鎖,使用 JUC 包下的
Lock實(shí)現(xiàn)。 - 手動(dòng)
Lock時(shí),unlock()必須放在finally塊中。
到此這篇關(guān)于Java中Synchronized與Lock鎖機(jī)制的區(qū)別詳解的文章就介紹到這了,更多相關(guān)Java Synchronized與Lock區(qū)別內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
使用vscode搭建javaweb項(xiàng)目的詳細(xì)步驟
我個(gè)人是很喜歡VsCode的,開源免費(fèi)、功能全面,所以為了方便,我把我?guī)缀跛械倪\(yùn)行都集成到了VsCode上來,JavaWeb也不例外,下面這篇文章主要給大家介紹了關(guān)于使用vscode搭建javaweb項(xiàng)目的相關(guān)資料,需要的朋友可以參考下2022-11-11
MyBatis中多對(duì)一和一對(duì)多數(shù)據(jù)的處理方法
這篇文章主要介紹了MyBatis中多對(duì)一和一對(duì)多數(shù)據(jù)的處理,本文通過示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-01-01
Spring 中 BeanFactoryPostProcessor 的作用和示例源碼分析
Spring的BeanFactoryPostProcessor是容器初始化的擴(kuò)展接口,允許在Bean實(shí)例化前修改或擴(kuò)展Bean的配置元數(shù)據(jù),本文給大家介紹Spring 中 BeanFactoryPostProcessor 的作用和示例源碼分析,感興趣的朋友一起看看吧2025-03-03
java 靜態(tài)代理 動(dòng)態(tài)代理深入學(xué)習(xí)
代理模式是常用的java設(shè)計(jì)模式,特征是代理類與委托類有同樣的接口,代理類主要負(fù)責(zé)為委托類預(yù)處理消息、過濾消息、把消息轉(zhuǎn)發(fā)給委托類,以及事后處理消息等,需要的朋友可以參考下2012-11-11
SpringBoot請(qǐng)求體缺失異常原因分析與處理方案
這篇文章主要介紹了SpringBoot請(qǐng)求體缺失異常原因分析與處理方案,該異常是因?yàn)閁serController的edit方法缺失了預(yù)期的UserUpdatePwdDTO請(qǐng)求體,產(chǎn)生原因主要由前端未發(fā)送請(qǐng)求體、Content-Type設(shè)置錯(cuò)誤或后端缺少@RequestBody注解導(dǎo)致,需要的朋友可以參考下2025-10-10
SpringBoot實(shí)現(xiàn)數(shù)據(jù)加密脫敏的示例代碼
這篇文章主要為大家學(xué)習(xí)介紹了SpringBoot如何利用注解+反射+AOP實(shí)現(xiàn)數(shù)據(jù)加密脫敏的功能,文中的示例代碼講解詳細(xì),需要的可以參考一下2023-08-08
關(guān)于重寫equals()方法和hashCode()方法及其簡(jiǎn)單的應(yīng)用
這篇文章主要介紹了關(guān)于重寫equals()方法和hashCode()方法及其簡(jiǎn)單的應(yīng)用,網(wǎng)上的知識(shí)有些可能是錯(cuò)誤的,關(guān)于?equals()?方法的理解,大家討論不一樣,需要的朋友可以參考下2023-04-04

