1. 核心概念與定位
synchronized:
Java 內置的關鍵字,屬于 JVM 層面的隱式鎖。通過在方法或代碼塊上聲明,自動實現鎖的獲取與釋放,無需手動操作。設計目標是提供簡單易用的基礎同步能力,適合大多數常規同步場景。ReentrantLock:
位于?java.util.concurrent.locks
?包下的類,實現了?Lock
?接口,屬于顯式鎖。需要通過?lock()
?手動獲取鎖,unlock()
?手動釋放鎖(通常在?finally
?塊中執行)。設計目標是提供更靈活的同步控制,滿足復雜場景需求。
2. 核心共性
- 可重入性:兩者都是可重入鎖,即同一線程可以多次獲取同一把鎖(如遞歸調用同步方法),不會產生死鎖。
- 線程互斥:核心功能一致,都能保證同一時間只有一個線程進入臨界區,實現線程安全。
3. 關鍵區別
???(1)鎖的獲取與釋放方式
- synchronized 是隱式鎖,自動獲取和釋放,無需手動操作
- ReentrantLock 是顯式鎖,必須通過?
lock()
?獲取鎖,unlock()
?釋放鎖 - ReentrantLock 必須在 finally 塊中釋放鎖,否則可能因異常導致鎖無法釋放,造成死鎖
- synchronized 更簡潔,ReentrantLock 更靈活但需更小心使用
// synchronized 示例
public class SynchronizedExample {private int count = 0;// 隱式獲取和釋放鎖public synchronized void increment() {count++; // 臨界區}
}// ReentrantLock 示例
import java.util.concurrent.locks.ReentrantLock;public class ReentrantLockExample {private int count = 0;private final ReentrantLock lock = new ReentrantLock();// 顯式獲取和釋放鎖public void increment() {lock.lock(); // 顯式獲取鎖try {count++; // 臨界區} finally {lock.unlock(); // 顯式釋放鎖(必須在finally中)}}
}
? ?(2)嘗試獲取鎖與超時機制
? ?ReentrantLock 支持超時獲取鎖:
- ReentrantLock 的?
tryLock()
?方法可以嘗試獲取鎖而不阻塞,或設置超時時間 - 超時機制可以避免線程無限期等待鎖,提高系統的靈活性和穩定性
? ?synchronized 不支持超時機制:
- synchronized 一旦開始等待,就必須等到鎖釋放,無法主動放棄
// ReentrantLock 支持嘗試獲取鎖和超時
public class LockWithTimeout {private final ReentrantLock lock = new ReentrantLock();public boolean tryDoSomething() throws InterruptedException {// 嘗試獲取鎖,最多等待1秒if (lock.tryLock(1, TimeUnit.SECONDS)) {try {// 執行操作return true;} finally {lock.unlock();}}return false; // 獲取鎖失敗}
}// synchronized 不支持超時,必須一直等待
public class SynchronizedNoTimeout {public synchronized void doSomething() {// 無法設置超時,必須等待鎖釋放}
}
? (3)可中斷特性
???ReentrantLock 支持中斷:
- ReentrantLock 的?
lockInterruptibly()
?方法允許線程在等待鎖的過程中響應中斷 - 可中斷特性在需要取消任務或關閉服務時非常有用
? ?synchronized 不支持中斷:
- synchronized 等待鎖的過程無法被中斷,可能導致線程一直阻塞
// ReentrantLock 支持中斷
public class InterruptibleLock {private final ReentrantLock lock = new ReentrantLock();public void doWithInterrupt() throws InterruptedException {// 可被中斷的鎖獲取lock.lockInterruptibly();try {// 執行操作} finally {lock.unlock();}}
}// synchronized 不支持中斷
public class SynchronizedNotInterruptible {public synchronized void doSomething() {// 即使線程被中斷,仍會繼續等待鎖}
}
(4)公平鎖實現
什么是 “公平鎖” 與 “非公平鎖”:
- 公平鎖:線程獲取鎖的順序嚴格遵循 “請求鎖的先后順序”(FIFO),先請求的線程一定先拿到鎖,不會出現 “后請求的線程插隊” 的情況。
- 非公平鎖:線程獲取鎖時不嚴格遵循請求順序,允許 “插隊”—— 即使隊列中有等待的線程,新到達的線程也可以嘗試直接搶占鎖,搶占失敗后再進入隊列排隊。
ReentrantLock 可實現公平鎖:
- ReentrantLock 可以通過構造函數參數?
true
?創建公平鎖 - 公平鎖保證線程獲取鎖的順序與請求順序一致,避免線程饑餓
- 非公平鎖允許線程 "插隊" 獲取鎖,可能導致某些線程長時間等待,但性能更高
synchronized 只能是非公平鎖:
- synchronized 始終是非公平鎖,無法改為公平鎖
// ReentrantLock 可創建公平鎖
public class FairLockExample {// 公平鎖:按請求順序獲取鎖private final ReentrantLock fairLock = new ReentrantLock(true);public void doWithFairLock() {fairLock.lock();try {// 執行操作} finally {fairLock.unlock();}}
}// synchronized 只能是非公平鎖
public class SynchronizedNonFair {// 無法設置為公平鎖,始終是非公平的public synchronized void doSomething() {// 執行操作}
}
為什么synchronized 非公平,不支持公平鎖?:
synchronized
?的實現依賴 JVM 底層的?對象監視器(Monitor),其鎖分配邏輯本質是 “優先讓當前可執行的線程獲取鎖,減少線程切換開銷”,具體體現在兩個關鍵場景:
場景 1:新線程請求鎖時,直接 “插隊” 搶占
當一個線程 A 釋放鎖時,JVM 并不會立刻喚醒等待隊列中最前面的線程 B(公平鎖邏輯),而是會先檢查?當前是否有新線程 C 正在請求鎖。
如果有新線程 C,JVM 會允許 C 直接搶占鎖(無需進入等待隊列),只有當沒有新線程時,才會喚醒隊列中的 B。
為什么這么做?
線程從 “等待狀態” 被喚醒,需要經歷?內核態→用戶態?的切換(操作系統級操作),這個過程開銷較大;而新線程 C 本身處于 “運行態”,直接讓它獲取鎖可以避免一次線程切換,顯著提升性能。
例如:
- 線程 A 釋放鎖時,等待隊列中有線程 B(已等待 10ms);
- 此時線程 C 剛執行到?
synchronized
?代碼塊,請求鎖; - JVM 會讓 C 直接拿到鎖,B 繼續等待;
- 只有當 A 釋放鎖時沒有新線程,才喚醒 B。
場景 2:線程重入時,無需排隊
synchronized
?是?可重入鎖(同一線程可多次獲取同一把鎖),而重入邏輯本身就是 “非公平” 的 —— 線程再次請求已持有的鎖時,無需進入等待隊列,直接成功獲取。
這是因為:線程持有鎖時,本身就有權限訪問臨界區,重入時跳過排隊是合理的,且能避免 “自己等自己” 的死鎖問題。但從公平性角度看,這相當于 “持有鎖的線程插隊”,優先于隊列中的其他線程。
例如:
- 線程 A 已持有鎖,執行到一個嵌套的?
synchronized
?代碼塊; - 此時等待隊列中有線程 B;
- 線程 A 無需排隊,直接重入鎖,B 繼續等待。
公平性的性能代價:
代價 1:強制線程切換,增加開銷
公平鎖要求嚴格按 “請求順序” 分配鎖,這意味著:
- 當鎖釋放時,必須喚醒等待隊列中最前面的線程(不能讓新線程插隊);
- 被喚醒的線程需要從 “等待態” 切換到 “運行態”,這個過程涉及操作系統內核操作,開銷遠大于 “新線程直接搶占”。
代價 2:鎖競爭激烈時,吞吐量下降
公平鎖會導致 “等待隊列越長,新線程越難獲取鎖”,即使新線程能快速執行完臨界區,也必須排隊。
例如:
- 等待隊列中有 10 個線程,每個線程執行臨界區需要 100ms;
- 此時有一個新線程,臨界區僅需 1ms;
- 公平鎖下,新線程必須排在第 11 位,等待 10×100ms=1000ms 后才能執行,總耗時 1001ms;
- 非公平鎖下,新線程可以直接搶占,總耗時 1ms(新線程)+ 10×100ms(隊列線程)= 1001ms?不 —— 實際是新線程執行 1ms 后釋放鎖,隊列中的第一個線程立刻執行,總耗時會更短(1ms + 100ms + ...),因為減少了一次線程切換的等待。
4.總結
ReentrantLock 和 synchronized 的核心區別可以概括為:
特性 | ReentrantLock | synchronized |
---|---|---|
鎖操作方式 | 顯式(lock/unlock) | 隱式(自動獲取釋放) |
靈活性 | 高,支持多種獲取方式 | 低,固定的獲取方式 |
超時獲取 | 支持 | 不支持 |
可中斷性 | 支持 | 不支持 |
公平性 | 可選擇 | 僅非公平 |
鎖狀態查詢 | 可查詢(isLocked () 等) | 不可查詢 |
使用復雜度 | 較高,需手動釋放 | 低,不易出錯 |
synchronized 是 “簡單易用的基礎方案”,ReentrantLock 是 “靈活可控的高級方案”。現代 Java 版本中兩者性能差異不大,選擇時主要依據功能需求:簡單場景用 synchronized,復雜場景用 ReentrantLock。
適用場景:
優先用 synchronized:
簡單同步場景(如普通方法 / 代碼塊同步)、追求代碼簡潔性、低并發場景。
優勢:無需手動釋放鎖,減少出錯概率,JVM 持續優化(如偏向鎖、輕量級鎖)。優先用 ReentrantLock:
需要超時獲取鎖、可中斷鎖、公平鎖的場景;需要多個條件變量精確控制線程喚醒;復雜同步邏輯(如生產者 - 消費者模型的精細化控制)。