AQS之ReentrantReadWriteLock寫鎖

雪中孤狼發表於2021-01-22

1. 用法

1.1 定義一個安全的list集合

public class LockDemo  {
ArrayList<Integer> arrayList = new ArrayList<>();//定義一個集合
// 定義讀鎖
ReentrantReadWriteLock.ReadLock readLock = new    ReentrantReadWriteLock(true).readLock();
// 定義寫鎖
ReentrantReadWriteLock.WriteLock writeLock = new ReentrantReadWriteLock(true).writeLock();

public void addEle(Integer ele) {
writeLock.lock(); // 獲取寫鎖
arrayList.add(ele);
writeLock.unlock(); // 釋放寫鎖
}
public Integer getEle(Integer index) {
try{
readLock.lock(); // 獲取讀鎖
Integer res = arrayList.get(index);
return res;
} finally{
readLock.unlock();// 釋放讀鎖
}
}
}

1.2 Sync 原始碼中的屬性與方法在上一篇文章中已經講過了

2. 獲取寫鎖原始碼分析

ReentrantReadWriteLock中的lock方法

public void lock() {
sync.acquire(1);
}

AbstractQueuedSynchronizer中的acquire方法

public final void acquire(int arg) {
    // 獲取鎖失敗則進入阻塞佇列
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}

acquireQueued(addWaiter(Node.EXCLUSIVE), arg))****,中的acquireQueued方法和addWaiter方法在前面的文章中都已經進行了詳細的解釋說明。
在這裡插入圖片描述

ReentrantReadWriteLock中的tryAcquire方法

protected final boolean tryAcquire(int acquires) {
    // 獲取當前執行緒
    Thread current = Thread.currentThread();
    //  獲取狀態
    int c = getState();
    // 計算寫執行緒數量就是獨佔鎖的可從入數量
    int w = exclusiveCount(c);
    // 當前同步狀態state != 0,說明已經有其他執行緒獲取了讀鎖或寫鎖
    if (c != 0) {
       // 當前state不為0,此時:如果寫鎖狀態為0說明讀鎖此時被佔用返回false;
       // 如果寫鎖狀態不為0且寫鎖沒有被當前執行緒持有返回false
        if (w == 0 || current != getExclusiveOwnerThread())
            return false;
       // 判斷同一執行緒獲取寫鎖是否超過最大次數(65535),支援可重入     
        if (w + exclusiveCount(acquires) > MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
         //更新狀態
         //此時當前執行緒已持有寫鎖,現在是重入,所以只需要修改鎖的數量即可
        setState(c + acquires);
        return true;
    }
      //到這裡說明此時c=0,讀鎖和寫鎖都沒有被獲取
      //writerShouldBlock表示是否阻塞
    if (writerShouldBlock() ||
        !compareAndSetState(c, c + acquires))
        return false;
        // 設定鎖為當前執行緒所有
    setExclusiveOwnerThread(current);
    return true;
}
static final class FairSync extends Sync {
// 寫鎖是否應該被阻塞
    final boolean writerShouldBlock() {
return hasQueuedPredecessors();
}
}

3. 獲取寫鎖流程圖

3.1 流程圖獲取寫鎖過程

在這裡插入圖片描述

3.2 流程圖獲取寫鎖過程解析

寫鎖的獲取過程如下:

  1. 首先獲取c、w。c表示當前鎖狀態;w表示寫執行緒數量。然後判斷同步狀態state是否為0。如果state!=0,說明已經有其他執行緒獲取了讀鎖或寫鎖。
  2. 如果鎖狀態不為零(c != 0),而寫鎖的狀態為0(w = 0),說明讀鎖此時被其他執行緒佔用,所以當前執行緒不能獲取寫鎖,自然返回false。或者鎖狀態不為零,而寫鎖的狀態也不為0,但是獲取寫鎖的執行緒不是當前執行緒,則當前執行緒也不能獲取寫鎖。
  3. 判斷當前執行緒獲取寫鎖是否超過最大次數,若超過,拋異常,反之更新同步狀態(此時當前執行緒已獲取寫鎖,更新是執行緒安全的),返回true。
  4. 如果state為0,此時讀鎖或寫鎖都沒有被獲取,判斷是否需要阻塞(公平和非公平方式實現不同),在非公平策略下總是不會被阻塞,在公平策略下會進行判斷(判斷同步佇列中是否有等待時間更長的執行緒,若存在,則需要被阻塞,否則,無需阻塞),如果不需要阻塞,則CAS更新同步狀態,若CAS成功則返回true,失敗則說明鎖被別的執行緒搶去了,返回false。如果需要阻塞則也返回false。
  5. 成功獲取寫鎖後,將當前執行緒設定為佔有寫鎖的執行緒,返回true。
  6. 獲取鎖失敗的話,將當前執行緒進行放入阻塞佇列中。

4. 釋放寫鎖原始碼分析

ReentrantReadWriteLock中的unlock方法

public void unlock() {
    sync.release(1);
}

AbstractQueuedSynchronizer中的release方法

public final boolean release(int arg) {
    // 如果返回true 那麼釋放成功了
    if (tryRelease(arg)) {
        Node h = head;
        // 如果頭部不為空,並且頭節點的waitStatus是喚醒狀態那麼喚醒後繼執行緒
        if (h != null && h.waitStatus != 0)
         // 喚醒後繼執行緒
            unparkSuccessor(h);
        return true;
    }
    return false;
}

ReentrantReadWriteLock中tryRelease方法

protected final boolean tryRelease(int releases) {
// 若鎖的持有者不是當前執行緒,丟擲異常
    if (!isHeldExclusively())
     // 非法的監控器異常
        throw new IllegalMonitorStateException();
        // 計算寫鎖的新執行緒數
    int nextc = getState() - releases;
    // 如果獨佔模式重入數為0了,說明獨佔模式被釋放
    boolean free = exclusiveCount(nextc) == 0;
    if (free)
     // 設定獨佔執行緒為空
        setExclusiveOwnerThread(null);
    // 設定寫鎖的新執行緒數
    // 不管獨佔模式是否被釋放,更新獨佔重入數
    setState(nextc);
    return free;
}
protected final boolean isHeldExclusively() {
    // 若當前執行緒是當前鎖的持有執行緒那麼返回true
    return getExclusiveOwnerThread() == Thread.currentThread();
}

5. 釋放寫鎖流程圖

5.1 流程圖釋放過程

在這裡插入圖片描述

5.2 流程圖釋放過程解析

寫鎖的釋放過程:

  1. 首先檢視當前執行緒是否為寫鎖的持有者,如果不是丟擲異常。然後檢查釋放後寫鎖的執行緒數是否為0,如果為0則表示寫鎖空閒了,釋放鎖資源將鎖的持有執行緒設定為null,否則釋放僅僅只是一次重入鎖而已,並不能將寫鎖的執行緒清空。
  2. 說明:此方法用於釋放寫鎖資源,首先會判斷該執行緒是否為獨佔執行緒,若不為獨佔執行緒,則丟擲異常,否則,計算釋放資源後的寫鎖的數量,若為0,表示成功釋放,資源不將被佔用,否則,表示資源還被佔用。

6. 總結

6.1 state 解析

private volatile int state;

int 型別佔有 4個位元組一個位元組8位,所以 state 一個 32 位,高 16 位 代表讀鎖 低 16 位代表 寫鎖。

// 0x0000FFFF 16 進位制
// 1111111111111111 2 進位制
// 65535 10 進位制
static final int SHARED_SHIFT   = 16;
static final int SHARED_UNIT    = (1 << SHARED_SHIFT); // 65536
static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1; //65535  
// 1111111111111111
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1; // 65535 
// 1111111111111111

如果此時同步狀態位 c 那麼獲取寫狀態 c & EXCLUSIVE_MASK
如果此時同步狀態位 c 那麼獲取讀狀態 c >>>16 無符號補0,右移16位

6.2 注意

以上便是ReentrantReadWriteLock中寫鎖的分析,下一篇文章將是Condition的分析,如有錯誤之處,幫忙指出及時更正,謝謝,如果喜歡謝謝點贊加收藏加轉發(轉發註明出處謝謝!!!)

相關文章