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

ReadWriteLock 與 ReentrantReadWriteLock 深度解析:讀寫鎖的 AQS 實現原理

     編輯:Java 相關知識

`ReentrantReadWriteLock` 是 Java 並發包中實現「讀讀共存、讀寫互斥、寫寫互斥」的核心鎖。本文從接口定義、內部結構、狀態位運算、獲取/釋放邏輯、公平與非公平策略、重入機制等維度,深度剖析其基於 AQS 的實現原理。

一、接口與類結構總覽

// 接口定義
public interface ReadWriteLock {
    Lock readLock();   // 共享鎖
    Lock writeLock();  // 獨占鎖
}

// 實現類
public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializable {
    private final ReadLock readerLock;
    private final WriteLock writerLock;
    final Sync sync;  // 核心同步器,繼承 AQS

    public ReentrantReadWriteLock() { this(false); }
    public ReentrantReadWriteLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
        readerLock = new ReadLock(this);
        writerLock = new WriteLock(this);
    }
    public Lock readLock()  { return readerLock; }
    public Lock writeLock() { return writerLock; }
    
    // 內部類:ReadLock、WriteLock 實現 Lock 接口,委託 sync
    // 內部類:Sync 繼承 AQS,實現 tryAcquire/tryRelease/tryAcquireShared/tryReleaseShared
    // 內部類:FairSync、NonfairSync 繼承 Sync
}

二、核心設計:一個 int 狀態存雙狀態(位運算)

AQS 只有一個 `int state`(32 位),RWLock 巧妙拆分:

  • 高 16 位:讀狀態(共享計數),表示當前持有讀鎖的線程數/重入次數總和。
  • 低 16 位:寫狀態(獨占計數),表示持有寫鎖的線程重入次數。
static final int SHARED_SHIFT   = 16;
static final int SHARED_UNIT    = 1 << SHARED_SHIFT;  // 0x00010000
static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1; // 0xFFFF
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1; // 0xFFFF

static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }      // 高 16 位
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }      // 低 16 位

狀態變更運算

  • 讀鎖獲取:`state + SHARED_UNIT`(即 `state + (1 << 16)`)
  • 寫鎖獲取:`state + 1`
  • 讀鎖釋放:`state - SHARED_UNIT`
  • 寫鎖釋放:`state - 1`

三、寫鎖(獨占鎖)獲取與釋放

tryAcquire(int acquires)

protected final boolean tryAcquire(int acquires) {
    Thread current = Thread.currentThread();
    int c = getState();
    int w = exclusiveCount(c);  // 當前寫狀態
    
    if (c != 0) {  // 有鎖被持有(讀或寫)
        if (w == 0 || current != getExclusiveOwnerThread()) 
            return false;  // 有讀鎖、或已有其他線程持有寫鎖 -> 失敗
        if (w + exclusiveCount(acquires) > MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
        setState(c + acquires);  // 重入:增加寫計數
        return true;
    }
    
    // 無鎖被持有
    if (writerShouldBlock() || !compareAndSetState(c, c + acquires))
        return false;
    setExclusiveOwnerThread(current);
    return true;
}

關鍵點解析

  • 讀寫互斥:`c != 0 && w == 0` 表示有讀鎖,寫鎖獲取失敗。
  • 重入:同一線程可重複獲取寫鎖,計數疊加。
  • writerShouldBlock():公平鎖下檢查同步隊列是否有前驅節點(`hasQueuedPredecessors()`),非公平鎖直接返回 `false`(搶佔)。

tryRelease(int releases)

protected final boolean tryRelease(int releases) {
    if (!isHeldExclusively()) throw new IllegalMonitorStateException();
    int nextc = getState() - releases;
    boolean free = exclusiveCount(nextc) == 0;
    if (free) setExclusiveOwnerThread(null);
    setState(nextc);
    return free;  // 完全釋放時返回 true,喚醒等待線程
}

四、讀鎖(共享鎖)獲取與釋放

tryAcquireShared(int unused)

邏輯較複雜,核心要點:

  1. 寫鎖佔用時直接失敗:`exclusiveCount(c) != 0` 且持有者非當前線程 → 返回 -1。
  2. 非公平鎖:無視隊列,直接 CAS 增加讀計數(`state + SHARED_UNIT`)。
  3. 公平鎖:若隊列有前驅節點 → 返回 -1 排隊。
  4. 線程級重入計數:每個線程的讀重入次數存儲在 `ThreadLocalHoldCounter` 中,總計數存儲在 `state` 高 16 位。
  5. 溢出檢查:`sharedCount(nextc) > MAX_COUNT` 拋錯。

tryReleaseShared(int unused)

protected final boolean tryReleaseShared(int unused) {
    Thread current = Thread.currentThread();
    // 1. 遞減線程級 HoldCounter
    // 2. CAS 遞減 state 高 16 位
    // 3. 若釋放後 sharedCount == 0 返回 true 喚醒寫線程
    // ... (源碼較長,核心是減少共享計數)
}

五、公平 vs 非公平策略差異

預設構造為非公平鎖(`new ReentrantReadWriteLock()`),高併發讀多寫少場景推薦非公平。

六、鎖降級:寫鎖 → 讀鎖(單向)

支援從寫鎖降級為讀鎖,不支援升級:

rwLock.writeLock().lock();
try {
    // 更新數據
    data = compute();
    // 降級:先獲取讀鎖,再釋放寫鎖
    rwLock.readLock().lock();
} finally {
    rwLock.writeLock().unlock(); // 必須在 finally 中釋放寫鎖
}
// 此處持有讀鎖,可安全讀取 data
try {
    use(data);
} finally {
    rwLock.readLock().unlock();
}

降級保證數據可見性:寫鎖釋放前獲取讀鎖,利用 AQS 狀態變更的 Happens-Before 關係,確保其他線程後續獲取讀鎖能看到最新數據。

七、性能優化建議

  1. 讀多寫少:RWLock 顯著優於 `ReentrantLock`/`synchronized`。
  2. 讀寫均衡或寫多:RWLock 開銷大(位運算、ThreadLocal、雙隊列),反而不如獨占鎖。
  3. 關鍵區極短:考慮 `StampedLock`(樂觀讀、無鎖讀)或無鎖數據結構。
  4. 避免長時間持有讀鎖:會阻塞寫線程,導致寫飢餓。

八、總結

`ReentrantReadWriteLock` 通過位分離雙狀態、AQS 共享/獨占模式、ThreadLocal 線程級計數 實現高效讀寫分離。核心特性:讀讀共存、讀寫互斥、寫寫互斥、支援重入、支援降級、公平/非公平可選。正確使用關鍵在於:識別讀多寫少場景、嚴格遵循降級順序、避免在鎖內執行耗時/阻塞操作。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
場景非公平公平
寫鎖獲取直接 CAS 搶佔若隊列有前驅則排隊
讀鎖獲取直接 CAS 搶佔若隊列有前驅則排隊
吞吐量高低
飢餓風險寫線程可能飢餓無飢餓