源碼解讀之(五)ReentrantReadWriteLock(下)

上篇的中篇主要講的ReentrantReadWriteLockReadLock的加鎖和減鎖過程:
源碼解讀之(五)ReentrantReadWriteLock(中)
這篇下篇主要來講解講解ReentrantReadWriteLockWriteLock的加鎖和減鎖過程。


四、WriteLock

ReentrantReadWriteLockWriteLock加鎖解鎖過程依賴於AbstractQueuedSynchronizer(AQS)類,所以有些相同的邏輯可以看看ReentrantLock的邏輯。

1、加鎖過程

  • WriteLock的lock()內部通過sync.acquire(1)獲取鎖。
  • 嘗試通過tryAcquire獲取寫鎖,如果獲取成功那麼就成功佔用寫鎖。
  • 獲取寫鎖失敗後,將當前線程添加到寫鎖喚醒隊列當中acquireQueued(addWaiter(Node.EXCLUSIVE), arg))。
  • acquireQueued(addWaiter(Node.EXCLUSIVE), arg))的操作邏輯和ReentrantLock的邏輯一致。
public static class WriteLock implements Lock, java.io.Serializable {
    private static final long serialVersionUID = -4992448646407690164L;
    private final Sync sync;

    protected WriteLock(ReentrantReadWriteLock lock) {
        sync = lock.sync;
    }

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

public final void acquire(int arg) {
    // 1、先嚐試獲取鎖tryAcquire
    // 2、獲鎖失敗就addWaiter操作
    // 3、acquireQueued判斷是否喚醒
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

tryAcquire過程

  • 獲取鎖狀態state變量,並獲取寫鎖佔用的計數值。
  • 當前state不爲0,如果寫鎖狀態爲0說明讀鎖被佔用,返回鎖佔用失敗。
  • 鎖狀態state不爲空且佔鎖線程爲當前線程,說明鎖被其他線程佔用返回鎖佔用失敗。
  • 寫鎖重入數溢出,返回鎖佔用失敗。
  • 如果寫鎖阻塞 或者 設置state狀態失敗,返回鎖佔用失敗。
  • 設置當前鎖佔用線程爲當前線程,返回鎖佔用成功。
protected final boolean tryAcquire(int acquires) {
    Thread current = Thread.currentThread();
    // 獲取鎖狀態state變量
    int c = getState();
    // 獲取寫鎖佔用的計數
    int w = exclusiveCount(c);
    // 如果鎖狀態state不爲0
    if (c != 0) {
        // 1、當前state不爲0,如果寫鎖狀態爲0說明讀鎖此時被佔用,說明鎖被讀鎖佔用
        // 2、鎖狀態state不爲空且佔鎖線程爲當前線程(屬於鎖重入),說明鎖被其他線程佔用
        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;
    }
    // 如果寫鎖阻塞 或者 設置state狀態失敗,那麼就代表獲鎖失敗
    if (writerShouldBlock() ||
        !compareAndSetState(c, c + acquires))
        return false;
    // 設置當前鎖佔用線程爲當前線程
    setExclusiveOwnerThread(current);
    return true;
}

2、解鎖過程

  • WriteLock的unlock()內部通過sync. release(1)釋放鎖。
  • 嘗試通過tryRelease()方法來釋放鎖並喚醒下一個等待線程。
  • 在喚醒過程中需要仔細看看讀寫鎖等待線程喚醒的細節。
public void unlock() {
    sync.release(1);
}

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}
private void unparkSuccessor(Node node) {
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);
	
	// 如果狀態取消,從尾部向後遍歷以找到實際的未取消的節點
    Node s = node.next;
    if (s == null || s.waitStatus > 0) {
        s = null;
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    if (s != null)
        LockSupport.unpark(s.thread);
}

tryRelease過程

  • 判斷當前線程和鎖佔用線程不一致isHeldExclusively()拋出異常。
  • 鎖狀態減去當前釋放動作傳入參數nextc = getState() - releases。
  • 判斷鎖狀態的寫狀態爲0就表明當前線程已經完全釋放鎖。
  • 當前線程完全釋放鎖,然後設置鎖佔用線程爲null並設置鎖狀態。
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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章