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

Java ReadWriteLock 實現機制深度解析(二):可重入寫優先鎖與讀優先鎖

     編輯:Java 相關知識

承接上文

上篇解析了 Doug Lea 早期 concurrent 包中 WriterPreferenceReadWriteLock(寫優先鎖)的基礎實現。本文續析其可重入版本 ReentrantWriterPreferenceReadWriteLock 以及 讀優先鎖 ReaderPreferenceReadWriteLock 的核心差異與設計考量。

1. ReentrantWriterPreferenceReadWriteLock - 可重入寫優先鎖

核心設計目標

  • 允許同一線程重複獲取讀鎖或寫鎖(鎖計數器機制)
  • 保持寫優先語義:有寫線程等待時,新讀線程必須阻塞
  • 支援「寫線程可降級為讀」、「讀線程可升級為寫」(需謹慎使用)

新增關鍵成員變數

/** 當前持有寫鎖的線程重入次數 */
protected long writeHolds_ = 0;

/** 每個讀線程的重入計數:Key=Thread, Value=Integer(計數) */
protected HashMap readers_ = new HashMap();

/** 常量池優化:避免頻繁建立 Integer(1) */
protected static final Integer IONE = new Integer(1);

讀鎖核心邏輯重寫

// 判斷是否允許讀線程獲取鎖
protected boolean allowReader() {
    // 原邏輯:無活躍寫線程且無等待寫線程
    // 新增:若當前線程已持有寫鎖,允許重入讀(寫降級讀)
    return (activeWriter_ == null && waitingWriters_ == 0) ||
           activeWriter_ == Thread.currentThread();
}

// 獲取讀鎖
protected synchronized boolean startRead() {
    Thread t = Thread.currentThread();
    Object c = readers_.get(t);
    
    if (c != null) { // 已持有讀鎖,計數+1
        readers_.put(t, new Integer(((Integer)c).intValue() + 1));
        ++activeReaders_;
        return true;
    }
    
    if (allowReader()) { // 首次獲取
        readers_.put(t, IONE);
        ++activeReaders_;
        return true;
    }
    return false; // 需等待
}

// 釋放讀鎖
protected synchronized Signaller endRead() {
    Thread t = Thread.currentThread();
    Object c = readers_.get(t);
    if (c == null) throw new IllegalStateException();
    
    --activeReaders_;
    
    if (c != IONE) { // 重入計數 > 1,僅遞減
        int h = ((Integer)c).intValue() - 1;
        readers_.put(t, (h == 1) ? IONE : new Integer(h));
        return null; // 不喚醒其他線程
    } else { // 計數歸零,移除記錄
        readers_.remove(t);
        if (writeHolds_ > 0) 
            return null; // 當前線程仍持有寫鎖,不喚醒寫線程
        
        // 邏輯同父類:無讀者且有等待寫者 → 喚醒寫者
        if (activeReaders_ == 0 && waitingWriters_ > 0)
            return writerLock_;
        return null;
    }
}

寫鎖核心邏輯重寫

// 獲取寫鎖
protected synchronized boolean startWrite() {
    // 情況 1:當前線程已持有寫鎖 → 重入
    if (activeWriter_ == Thread.currentThread()) {
        ++writeHolds_;
        return true;
    }
    
    // 情況 2:無寫鎖重入記錄
    if (writeHolds_ == 0) {
        // 允許條件:無活躍讀者,或唯一活躍讀者就是當前線程(讀升級寫)
        if (activeReaders_ == 0 || 
            (readers_.size() == 1 && readers_.get(Thread.currentThread()) != null)) {
            activeWriter_ = Thread.currentThread();
            writeHolds_ = 1;
            return true;
        }
        return false;
    }
    return false; // 其他線程持有寫鎖
}

// 釋放寫鎖
protected synchronized Signaller endWrite() {
    --writeHolds_;
    if (writeHolds_ > 0) 
        return null; // 重入計數未歸零,繼續持有
    
    // 計數歸零,真正釋放
    activeWriter_ = null;
    if (waitingReaders_ > 0 && allowReader())
        return readerLock_; // 喚醒所有等待讀者
    else if (waitingWriters_ > 0)
        return writerLock_; // 喚醒一個等待寫者
    return null;
}

關鍵設計細節解析

場景處理邏輯設計意圖
寫線程重入寫鎖writeHolds_++ 直接成功避免死鎖,支援遞歸調用
寫線程重入讀鎖allowReader() 檢測 activeWriter_ == currentThread 允許支援鎖降級:寫→讀,釋放寫鎖前可讀取共享狀態
讀線程嘗試升級寫鎖startWrite() 檢測 readers_.size()==1 && 當前線程在內 允許支援鎖升級:讀→寫,極易導致死鎖(兩讀線程同時升級),生產慎用
釋放讀鎖但仍持有寫鎖endRead() 檢測 writeHolds_ > 0 不喚醒寫者確保寫鎖完全釋放前,不讓其他寫者搶佔

2. ReaderPreferenceReadWriteLock - 讀優先鎖

核心差異:僅重寫 allowReader()

protected boolean allowReader() {
    // 只要沒有線程持有寫鎖,即允許讀(忽略等待寫者)
    return activeWriter_ == null;
}

語義對比

特性寫優先讀優先
新讀者到來時若有等待寫者 → 阻塞若無活躍寫者 → 直接獲取
寫線程饑餓風險低(寫者優先獲取)高(持續讀流可無限阻塞寫者)
讀吞吐量較低(寫者等待期間新讀者阻塞)極高(讀者幾乎無阻塞)
典型適用場景寫操作延遲敏感、資料一致性要求高讀多寫極少、寫操作可容忍長延遲

為何實務極少用讀優先鎖?

  1. 寫線程饑餓:高併發讀場景下,寫線程可能永遠無法獲取鎖
  2. 資料不一致窗口放大:長時間讀併發期間,寫入被延遲,舊資料被持續讀取
  3. JDK ReentrantReadWriteLock 預設即公平/非公平寫優先:業界標準實踐傾向寫優先或公平策略

3. 從早期實現到 JDK 標準庫的演進

特性Doug Lea 早期版JDK ReentrantReadWriteLock
同步基礎自定義 Sync 介面 + wait/notifyAbstractQueuedSynchronizer (AQS) 狀態機
鎖狀態編碼分離變數 activeReaders_、writeHolds_單一 int state:高 16 位讀計數,低 16 位寫計數
公平性僅寫優先/讀優先兩種支援公平/非公平兩種模式(構造函數指定)
Condition 支援無寫鎖支援 newCondition(),讀鎖不支援
鎖降級支援支援(寫→讀)支援(需先獲寫鎖再獲讀鎖,最後釋放寫鎖)
鎖升級支援支援(讀→寫,風險自擔)不支援(嘗試升級會死鎖)

總結

ReentrantWriterPreferenceReadWriteLock 通過線程級計數器實現重入語義,並在allowReader、startWrite中引入當前線程身份判斷,優雅支援了鎖降級與受控升級。而 ReaderPreferenceReadWriteLock 雖實現簡單,但因寫饑餓風險鮮用於生產。現代 Java 開發應直接使用 java.util.concurrent.locks.ReentrantReadWriteLock(基於 AQS,效能更優、功能更全),僅在極特殊場景(如需自定義公平策略、鎖升級)時參考早期實現自行構建。

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