ReentrantReadWriteLock的readerShouldBlock與apparentlyFirstQueuedIsExclusive 深入理解讀鎖的非公平實現

前言

ReentrantReadWriteLock的讀鎖或寫鎖的獲取過程中,在CAS修改同步器狀態之前,會使用readerShouldBlockwriterShouldBlock來根據當前公平模式和當前同步隊列來得到當前線程是否可以繼續嘗試獲得鎖(CAS修改同步器狀態)。

readerShouldBlockwriterShouldBlock封裝掉了當前的公平模式(公平還是非公平的),無論是在獨佔鎖的獲取過程還是在共享鎖的獲取過程中,你會發現,公平實現與非公平實現的差異在於,公平實現會調用hasQueuedPredecessors,非公平實現則不會。

所以,XXXShouldBlockhasQueuedPredecessors之上進行了封裝,公平實現下,XXXShouldBlock肯定會去調用hasQueuedPredecessors的。

JUC框架 系列文章目錄

writerShouldBlock的非公平實現

我們首先看寫鎖的ShouldBlock的兩種實現。

//非公平實現
        final boolean writerShouldBlock() {
            return false; // writers can always barge
        }
//公平實現
        final boolean writerShouldBlock() {
            return hasQueuedPredecessors();
        }

可見writerShouldBlock在非公平實現下,是直接返回false的。這是一種完全的非公平實現。

當一個線程想要獲得寫鎖成功時,只有當前ReentrantReadWriteLock的寫鎖沒有被其他線程持有,且ReentrantReadWriteLock的讀鎖沒有被任意線程持有(注意這裏是任意線程,包括自己線程持有讀鎖)。所以writerShouldBlock的非公平實現直接返回false,代表你儘管去非公平地嘗試,反正想搶寫鎖成功,條件是很苛刻的。

readerShouldBlock的非公平實現

可見readerShouldBlock的實現並不是直接返false,而是調用apparentlyFirstQueuedIsExclusive。這不是一種完全的非公平實現,但這樣做是有它的道理的。

//非公平實現
        final boolean readerShouldBlock() {
            /* As a heuristic to avoid indefinite writer starvation,
             * block if the thread that momentarily appears to be head
             * of queue, if one exists, is a waiting writer.  This is
             * only a probabilistic effect since a new reader will not
             * block if there is a waiting writer behind other enabled
             * readers that have not yet drained from the queue.
             */
            return apparentlyFirstQueuedIsExclusive();
        }
//公平實現
        final boolean readerShouldBlock() {
            return hasQueuedPredecessors();
        }

    /**
     * Returns {@code true} if the apparent first queued thread, if one
     * exists, is waiting in exclusive mode.  If this method returns
     * {@code true}, and the current thread is attempting to acquire in
     * shared mode (that is, this method is invoked from {@link
     * #tryAcquireShared}) then it is guaranteed that the current thread
     * is not the first queued thread.  Used only as a heuristic in
     * ReentrantReadWriteLock.
     */
    final boolean apparentlyFirstQueuedIsExclusive() {
        Node h, s;
        return (h = head) != null &&
            (s = h.next)  != null &&
            !s.isShared()         &&
            s.thread != null;
    }

As a heuristic to avoid indefinite writer starvation, block if the thread that momentarily appears to be head of queue, if one exists, is a waiting writer. This is only a probabilistic effect since a new reader will not block if there is a waiting writer behind other enabled readers that have not yet drained from the queue.

翻譯過來就是,這只是一種啓發式地避免寫鎖無限等待的做法,它在遇到同步隊列的head後繼爲寫鎖節點時,會讓readerShouldBlock返回true代表新來的讀鎖(new reader)需要阻塞等待這個head後繼。但只是一定概率下能起到作用,如果同步隊列的head後繼是一個讀鎖,之後纔是寫鎖的話,readerShouldBlock就肯定會返回false了。

new reader意思就是,線程第一次來嘗試獲取讀鎖。

寫鎖無限等待 indefinite writer starvation

在這裏插入圖片描述

首先解釋一下indefinite writer starvation,上圖中,寫鎖節點作爲head後繼阻塞等待中。

考慮readerShouldBlock的現有實現的話,寫鎖節點只需要等待線程AB釋放讀鎖後,就可以獲得到寫鎖了。而線程CDE作爲new reader,不會去嘗試獲取讀鎖,而是將自己包裝成讀鎖節點排在寫鎖節點的後面。這個具體的流程,請查看ReentrantReadWriteLock源碼解析讀鎖的獲取章節內容裏面對fullTryAcquireShared函數的講解,我們只需要知道readerShouldBlock返回了true,代表讀鎖獲取應該阻塞,如果這個讀鎖是個new reader。

//非公平實現
        final boolean readerShouldBlock() {
            return false;
        }

但考慮readerShouldBlock如上代碼這樣實現的話,線程CDE即使作爲new reader,因爲讀讀不互斥,所以也會去獲取到讀鎖。這下好了,寫鎖節點需要等待線程ABCDE釋放讀鎖後,纔可以獲得到寫鎖了。

heuristic啓發式地防止new reader

在這裏插入圖片描述
但儘管readerShouldBlock是這樣的非公平實現,也無法防止上圖第二種情況的new reader的獲取讀鎖動作,所以說這只是一定概率下防止new reader獲取讀鎖,但有概率的防止總比啥都不做強。

總結

  • readerShouldBlock的非公平實現,並不是完全的非公平實現(即直接返回false)。
  • readerShouldBlock的不完全的非公平實現,是爲了防止寫鎖無限等待。
  • readerShouldBlock在一定概率下,防止了new reader的讀鎖獲取動作,轉而讓new reader去sync queue中排隊。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章