併發程式設計之——寫鎖原始碼分析

weixin_34377065發表於2018-04-30

1.前言

Java 中的讀寫鎖實現是 ReentrantReadWriteLock ,是一種鎖分離策略。能有效提高讀比寫多的場景下的程式效能。

關於如何使用參見 併發程式設計之 Java 三把鎖

由於讀寫鎖較為複雜,故分為篇文章進行原始碼分析,今天先說較為簡單的寫鎖。

2. 寫鎖介紹

不論是讀鎖還是寫鎖,都是基於 AQS 的,而 AQS 留給子類實現的就是 tryAcquire 或者 tryAcquireShared 方法,前者是寫鎖的實現,後者是讀鎖的實現,從名字上可以看出,一個是獨佔鎖,一個是共享鎖。

今天看的就是 tryAcquire 和 tryRelease 的實現,獲取和釋放。

3. tryAcquire 實現

原始碼加註釋如下:

// 如果有讀鎖,此時是獲取不到寫鎖的。當有寫鎖時,判斷重入次數。
// 當寫鎖空閒,讀鎖空閒,公平模式下,如果佇列中有等待的,不會搶鎖。非公平模式下,必搶鎖。
protected final boolean tryAcquire(int acquires) {
    // 寫
    Thread current = Thread.currentThread();
    int c = getState();
    // 用 state & 65535 得到低 16 位的值。
    int w = exclusiveCount(c);
    if (c != 0) {
        // (Note: if c != 0 and w == 0 then shared count != 0)
        // 如果 state 不是0,且低16位是0,說明了什麼?說明寫鎖是空閒的,讀鎖被霸佔了。那麼也不能拿鎖,返回 fasle。
        // 如果低 16 位不是0,說明寫鎖被霸佔了,並且,如果持有鎖的不是當前執行緒,那麼這次拿鎖是失敗的。返回 fasle。
        // 總之,當有讀鎖,就不能獲取寫鎖。當有寫鎖,就必須是重入鎖。
        if (w == 0 || current != getExclusiveOwnerThread())
            return false;
        // 到這一步了,只會是寫重入鎖。如果寫重入次數超過最大值 65535,就會溢位。
        if (w + exclusiveCount(acquires) > MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
        // Reentrant acquire
        // 將 state  + 1
        setState(c + acquires);
        return true;
    }
    // 當 state 是 0 的時候,那麼就可以獲取鎖了。
    // writerShouldBlock 判斷是否需要鎖。非公平情況下,返回 false。公平情況下,根據 hasQueuedPredecessors 結果判斷。
    // 當佇列中有鎖等待了,就返回 false 了。
    // 當是非公平鎖的時候,或者佇列中沒有等待節點的時候,嘗試用 CAS 修改 state。
    if (writerShouldBlock() ||
        !compareAndSetState(c, c + acquires))
        return false;
    // 修改成功 state 後,修改鎖的持有執行緒。
    setExclusiveOwnerThread(current);
複製程式碼

詳解的邏輯都寫在註釋裡了,可以對照原始碼檢視,這裡再次總結這個方法的邏輯。

  1. 首先判斷鎖是否空閒。

  2. 如果空閒,則根據公平與否判斷是否應該獲取鎖,當 writerShouldBlock 返回結果是false 的時候,就使用 CAS 修改 state 變數,獲取鎖。成功之後修改 AQS 持有執行緒。

  3. 如果不是空閒的,則判斷寫鎖是否是空閒的,這裡有 2 種情況:

    3.1 如果寫鎖空閒,但 state 不是0,說明有讀鎖,那麼就不能獲取鎖。

    3.2 如果寫鎖不空閒,判斷持有 AQS 鎖的執行緒是不是當前執行緒,如果不是,不能獲取,反之,可以獲取重入鎖。

4. tryRelease 實現

程式碼加註釋如下:

protected final boolean tryRelease(int releases) {
    // 是否持有當前鎖
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    // 計算 state 值
    int nextc = getState() - releases;
    // 計算寫鎖的狀態,如果是0,說明是否成功。
    boolean free = exclusiveCount(nextc) == 0;
    // 釋放成功,設定持有鎖的執行緒為 null。
    if (free)
        setExclusiveOwnerThread(null);
    // 設定 state
    setState(nextc);
    return free;
}
複製程式碼

這裡還是很簡單的,只是有一個地方需要注意:

    // 計算寫鎖的狀態,如果是0,說明是否成功。
    boolean free = exclusiveCount(nextc) == 0;
複製程式碼

這裡計算的只是 state 變數的低 16 的值,而不是整個 state 的值。雖然寫的時候,必然是序列的,但這裡計算的仍然是低 16 位的。

5. 總結

寫鎖在獲取鎖的時候,有幾個地方需要注意:當有讀鎖的時候,是不能獲取寫鎖的。寫鎖可以重入,但不能超過 65535 次。寫鎖狀態設定在 state 變數的低 16 位。

同時,在獲取鎖的時候,也會根據公平與否決定此次釋放需要獲取鎖。

如果是非公平的,直接嘗試CAS 修改 state ,獲取鎖。

如果是公平的,則根據 hasQueuedPredecessors 方法的返回值判斷,也就是如果佇列中有等待的執行緒,則依據公平策略,放棄此次獲取鎖的操作。反之,直接獲取鎖。

相關文章