首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

Java 並發進階:Lock 接口、ReentrantLock 與 ReadWriteLock 深度實戰

     編輯:Java 綜合教學

Java 並發進階:Lock 接口、ReentrantLock 與 ReadWriteLock 深度實戰

JDK 1.5 引入 java.util.concurrent.locks 包,提供了比 synchronized 更強大、更靈活的顯式鎖機制。核心接口 Lock 及其經典實現 ReentrantLock、ReentrantReadWriteLock,解決了內置鎖無法中斷、無超時、無公平性、單一條件隊列等痛點,是高併發系統開發的利器。

一、 Lock 接口核心 API 與語義

public interface Lock {
    void lock();                          // 獲取鎖,阻塞直到成功
    void lockInterruptibly() throws InterruptedException; // 可中斷獲取鎖
    boolean tryLock();                    // 非阻塞嘗試獲取鎖,立即返回結果
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException; // 超時獲取鎖
    void unlock();                        // 釋放鎖
    Condition newCondition();             // 獲取條件變量 (支持多條件隊列)
}

四種獲取鎖方式的對比與選型

方法阻塞行為可中斷超時控制典型場景
lock()無限阻塞否無通用互斥,確信能快速獲取鎖
tryLock()不阻塞,立即返回否無非關鍵路徑、無法容忍等待、嘗試性獲取
tryLock(time, unit)阻塞指定時間是是有明確 SLA 要求、避免死等、分布式鎖客端
lockInterruptibly()無限阻塞是無需響應取消信號、可中斷的長時間任務

二、 標準使用範式:try-finally 守護鎖釋放

鐵律: 獲取鎖與釋放鎖必須成對,且 unlock() 必須放在 finally 塊中,防止異常導致鎖永不釋放 (死鎖)。

Lock lock = new ReentrantLock();
// 模式 1: lock()
lock.lock();
try {
    // 臨界區操作
} finally {
    lock.unlock();
}

// 模式 2: tryLock()
if (lock.tryLock()) {
    try {
        // 臨界區操作
    } finally {
        lock.unlock();
    }
} else {
    // 獲取失敗,執行降級/拋異常/記錄日誌
}

// 模式 3: lockInterruptibly() - 必須拋出或捕獲 InterruptedException
public void doTask() throws InterruptedException {
    lock.lockInterruptibly();
    try {
        // 臨界區操作
    } finally {
        lock.unlock();
    }
}
注意:lockInterruptibly() 僅在等待鎖的阻塞狀態下響應中斷;一旦獲得鎖進入運行狀態,interrupt() 不會強制釋放鎖,只會設置中斷標誌位。

三、 ReentrantLock:可重入獨佔鎖深度解析

ReentrantLock 是 Lock 的唯一標準實現,支持可重入 (同一線程可多次獲取鎖,計數器累加)、公平/非公平 策略、條件變量 (Condition)。

1. 構造函數與公平性選擇

// 默認非公平鎖 (吞吐優先,可能導致線程飢餓)
ReentrantLock lock = new ReentrantLock();

// 公平鎖 (按請求順序授予鎖,吞吐較低,但避免飢餓)
ReentrantLock fairLock = new ReentrantLock(true);

高併發下默認非公平鎖性能優勢明顯,除非有嚴格公平性業務需求,否則不建議使用公平鎖。

2. 核心進階方法 (源碼級理解)

  • getHoldCount():當前線程持有鎖的重入次數。
  • isHeldByCurrentThread():當前線程是否持有鎖。
  • getQueueLength():等待獲取鎖的線程估計數。
  • hasQueuedThreads():是否有線程在等待。
  • hasQueuedThread(Thread):指定線程是否在等待。
  • isLocked():鎖是否被任意線程持有。
  • getOwner():返回持有鎖的線程 (僅限監控/診斷)。

3. 經典錯誤案例:局部變量導致鎖失效

// 錯誤示例:每次調用創建新鎖對象,無法互斥
public void insert(Thread t) {
    Lock lock = new ReentrantLock(); // 錯誤!局部變量
    lock.lock();
    try { ... } finally { lock.unlock(); }
}

// 正確示例:鎖為實例/類成員,多線程共享同一把鎖
private final Lock lock = new ReentrantLock(); // final 修飾防止引用變更
public void insert(Thread t) {
    lock.lock();
    try { ... } finally { lock.unlock(); }
}

四、 Condition:精細化線程協作 (替代 wait/notify)

Condition 將傳統 Object Monitor 的單一等待隊列拆分為多個條件隊列,實現「特定條件下等待、特定條件下喚醒」。

class BoundedBuffer {
    final Lock lock = new ReentrantLock();
    final Condition notFull  = lock.newCondition(); // 未滿條件
    final Condition notEmpty = lock.newCondition(); // 未空條件
    final Object[] items = new Object[100];
    int putptr, takeptr, count;

    public void put(T x) throws InterruptedException {
        lock.lock();
        try {
            while (count == items.length) notFull.await(); // 等待不滿
            items[putptr] = x;
            if (++putptr == items.length) putptr = 0;
            ++count;
            notEmpty.signal(); // 喚醒消費者
        } finally { lock.unlock(); }
    }

    public T take() throws InterruptedException {
        lock.lock();
        try {
            while (count == 0) notEmpty.await(); // 等待不空
            T x = (T) items[takeptr];
            if (++takeptr == items.length) takeptr = 0;
            --count;
            notFull.signal(); // 喚醒生產者
            return x;
        } finally { lock.unlock(); }
    }
}

優勢: 精準喚醒,避免虛假喚醒導致的無效競爭;支持多條件 (如「未滿」「未空」「優先級高」等) 獨立管理。

五、 ReadWriteLock & ReentrantReadWriteLock:讀寫分離鎖

針對「讀多寫少」場景,ReadWriteLock 分離讀鎖 (共享鎖)與寫鎖 (排他鎖):

  • 讀-讀不互斥: 多線程可同時持有讀鎖,大幅提升讀吞吐。
  • 讀-寫互斥: 有讀鎖時寫鎖阻塞;有寫鎖時讀鎖阻塞。
  • 寫-寫互斥: 單線程持有寫鎖。

核心 API

ReadWriteLock rwLock = new ReentrantReadWriteLock();
Lock readLock = rwLock.readLock();  // 共享鎖
Lock writeLock = rwLock.writeLock(); // 排他鎖

// 讀操作
readLock.lock();
try { return data; } finally { readLock.unlock(); }

// 寫操作
writeLock.lock();
try { data = newValue; } finally { writeLock.unlock(); }

關鍵特性與配置

特性說明
可重入性讀鎖/寫鎖均支持可重入;持有寫鎖可降級獲取讀鎖,但讀鎖不能升級為寫鎖 (防止死鎖)。
公平性構造函數 new ReentrantReadWriteLock(true) 啟用公平模式,按請求順序授予,避免寫線程飢餓。
鎖降級寫鎖 → 讀鎖 → 釋放寫鎖 → 釋放讀鎖,保證數據可見性,典型用於緩存更新場景。
Condition 支持僅 writeLock 支持 newCondition(),讀鎖不支持條件變量。
鍵方法getReadLockCount(), getReadHoldCount(), isWriteLocked(), getWriteHoldCount() 等監控方法。

性能對比示例:讀操作並發

同步方法 (synchronized) 強制串行;讀寫鎖允許並發讀,吞吐量隨線程數線性增長。

六、 Lock vs synchronized:選型決策矩陣

維度synchronizedLock (ReentrantLock)
易用性關鍵字,隱式獲取/釋放,簡潔API 調用,顯式 lock/unlock,易忘釋放
性能 (JDK 8+)偏向鎖/輕量級鎖優化極佳,低競爭下更優高競爭、長持鎖、公平鎖場景下更穩定
可中斷不支持支持 (lockInterruptibly)
超時獲取不支持支持 (tryLock(time, unit))
公平性非公平可選 (構造參數)
條件變量單一 wait/notify多 Condition 隊列
讀寫分離不支持ReentrantReadWriteLock
死鎖診斷困難提供 getOwner() 等監控 API

七、 實戰最佳實踐

  1. 默認選 synchronized: 簡單互斥、低競爭、代碼塊較短場景,synchronized 代碼最簡潔、JVM 優化最成熟。
  2. 需中斷/超時/公平/多條件 → Lock: 複雜同步邏輯、長耗時持鎖、響應取消信號、生產者消費者模型。
  3. 讀多寫少 → ReadWriteLock: 緩存、配置中心、註冊表等讀頻遠高於寫的場景。
  4. 鎖對象 final 修飾: 防止引用變更導致鎖失效。
  5. 縮小鎖粒度: 僅鎖保護共享狀態的最小代碼區,避免在鎖內調用外部服務/IO/睡眠。
  6. 避免嵌套不同鎖: 必須嵌套時,全局統一獲取順序,防止死鎖。
  7. 考慮無鎖替代: 原子類 (Atomic*)、並發集合 (ConcurrentHashMap)、LongAdder、ThreadLocal、不可變對象,往往比鎖更高效。

掌握 Lock 體系,不僅是 API 的熟悉,更是對並發控制語義 (互斥、共享、條件協作、公平性、可中斷性) 的深度理解。在實際架構中,靈活組合 synchronized、ReentrantLock、ReadWriteLock、原子類與無鎖數據結構,才能構建高性能、高可用的並發系統。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved