Java併發之ReentrantReadWriteLock原始碼解析(一)

北洛發表於2021-07-08

ReentrantReadWriteLock

前情提要:在學習本章前,需要先了解筆者先前講解過的ReentrantLock原始碼解析和Semaphore原始碼解析,這兩章介紹了很多方法都是本章的鋪墊。下面,我們進入本章正題ReentrantReadWriteLock。

ReentrantReadWriteLock與ReentrantLock的使用方式有些相似,它提供了讀鎖(ReadLock)和寫鎖(WriteLock),這兩種鎖都實現了java.util.concurrent.locks.Lock這一介面,允許呼叫lock()方法來搶鎖,呼叫unlock()釋放鎖,也有:lockInterruptibly()、tryLock()、tryLock(long time, TimeUnit unit)……等一系列搶鎖方法。

比如下面的RWHashMap,RWHashMap相比於Java原生實現的HashMap多了一重執行緒安全保障。我們在<1>處建立了一個可重入讀寫鎖ReentrantReadWriteLock,並且分別在<2>、<3>處根據<1>處建立的可重入讀寫鎖生成讀鎖(readLock)和寫鎖(writeLock),當我們要呼叫put()、clear()這些修改雜湊表內容的方法時,佔有寫鎖的執行緒能夠保證當前沒有其他執行緒修改或讀取雜湊表的資料。當我們呼叫get()或者allKeys()這些讀取資料的方法時,讀鎖能保證沒有其他的寫入執行緒修改雜湊表,並且讀鎖允許有多個執行緒同時讀取雜湊表的資料。

那麼我們來思考下,如果不限制讀寫執行緒按照一定規則來訪問/修改雜湊表會有什麼問題?我們知道,雜湊表的實現是陣列+連結串列的結構,陣列中的每個元素都是一個連結串列,當有一個鍵值對<K1,V1>要在雜湊表上建立對映時,會先計算出K1的雜湊值,然後用雜湊值對陣列長度求餘,算出一個不超過陣列長度的索引i,這個索引i即是存放鍵值對的連結串列,如果陣列對應索引i的位置為null,則代表連結串列還沒有元素,於是把鍵值對<K1,V1>存到陣列對應的索引i的位置上;當再進來一個鍵值對<K2,V2>,同樣算出要存放在索引i的位置,這時判斷陣列的索引i的位置不為null,會遍歷到連結串列最後一個節點,將鍵值對作為節點插入到連結串列。

那麼如果用併發的方式將<K1,V1>和<K2,V2>寫入到HashMap會有什麼問題呢?假設執行緒1寫入<K1,V1>時發現索引i的位置null,鍵值對可以作為連結串列的頭節點,填充在陣列索引i的位置上;同時執行緒2在寫入<K2,V2>時也發現索引i的位置為null,也要把鍵值對填充在陣列索引i的位置上。在兩個執行緒確定好寫入位置後,執行緒1先寫入,執行緒2後寫入,於是我們就丟失一個鍵值對。

那麼讀寫執行緒之間又為什麼需要互斥呢?之前說過HashMap是陣列+連結串列的結構,如果陣列的長度為10,雜湊表的元素數量為50,這時可以兼顧查詢和插入的效能,因為平均每個陣列的連結串列長度為5,但是當陣列長度為10,元素數量為5000,平均每個陣列的連結串列長度為500,那麼此時再連結串列查詢和插入元素都有些吃力了,此時HashMap會進行擴容,將原先陣列長度從10擴充套件到500,將5000個元素重新分散到500個連結串列中,平均每個連結串列的長度為10來保證查詢和插入的效能。如果讀寫執行緒不互斥,可能出現原先讀執行緒判斷K1在陣列索引i的連結串列上,但此時有寫執行緒在HashMap新增鍵值對,導致HashMap擴容,K1從索引i移動到索引j的位置上。導致字典明明有K1這個鍵,讀執行緒卻在索引i的連結串列上找不到這個鍵值對。

上面舉的寫寫並行、讀寫並行會產生的問題只是冰山一角,如果真的用並行的方式讀寫HashMap可能產生的問題會比我們想象中的多。因此,基於可重入讀寫鎖(ReentrantReadWriteLock)的RWHashMap能夠保證:當有多個寫執行緒要修改HashMap的資料時,寫鎖能夠保證寫執行緒必須按照序列的方式修改HashMap;當有多個讀執行緒要訪問HashMap的資料,多個讀執行緒可以同時進行,且讀鎖能夠保證當前沒有任何佔有寫鎖的執行緒,不會出現在查詢資料的同時,資料的位置被修改。

package org.example.ch3;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;


public class RWHashMap {


    private final Map<String, Object> m = new HashMap<>();
    private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    private final Lock r = rwl.readLock();
    private final Lock w = rwl.writeLock();

    public Object get(String key) {
        r.lock();
        try {
            return m.get(key);
        } finally {
            r.unlock();
        }
    }

    public List<String> allKeys() {
        r.lock();
        try {
            return new ArrayList<>(m.keySet());
        } finally {
            r.unlock();
        }
    }

    public Object put(String key, Object value) {
        w.lock();
        try {
            return m.put(key, value);
        } finally {
            w.unlock();
        }
    }

    public void clear() {
        w.lock();
        try {
            m.clear();
        } finally {
            w.unlock();
        }
    }
}

  

學習過ReentrantLock的朋友都知道,當有執行緒佔有了可重入互斥鎖,如果還有別的執行緒請求鎖,這些執行緒會形成一個獨佔(Node.EXCLUSIVE)節點進入在ReentrantLock內部維護的一個等待佇列並陷入阻塞,當鎖被釋放時會喚醒佇列中等待鎖時間最長的執行緒起來競爭鎖,即頭節點的後繼節點對應的執行緒。

ReentrantReadWriteLock作為ReentrantLock的兄弟,其實現和ReentrantLock有些相似,只不過,ReentrantLock只能被一個執行緒獨佔,因此等待佇列中的每個節點都是獨佔節點,每個執行緒都是互斥的;而ReentrantReadWriteLock等待佇列可能有的獨佔節點,也可能有共享(Node.SHARED)節點,即:有的執行緒會獨佔寫鎖,有的執行緒會共享讀鎖。

我們用Wn代表寫執行緒,用Rn代表讀執行緒,n代表執行緒id,以公平模式下的可重入讀寫鎖為例。假如執行緒1已經獨佔了讀寫鎖的寫鎖,此時不管是讀執行緒還是寫執行緒過來,都只能乖乖入隊。這時執行緒2和執行緒3請求讀鎖,由於寫鎖被佔用,執行緒2和執行緒3只能先入隊,所以佇列的節點分佈為:head->R2->R3(tail),執行緒2和執行緒3入隊後,執行緒4請求寫鎖,此時也只能先入隊,佇列的節點分佈為:head->R2->R3->W4(tail),執行緒4入隊後,執行緒5、6、7請求讀鎖,此時佇列的節點分佈為:head->R2->R3->W4->R5->R6->R7(tail)。

當執行緒1釋放了寫鎖,會喚醒阻塞中的執行緒R2,R2被喚醒後獲取到讀鎖,並且判斷自己的後繼節點為共享節點會喚醒R3。R3被喚醒後會接著判斷後繼節點是否是共享節點,R3的後繼節點是獨佔節點W4,所以這裡不會喚醒W4。R2和R3在釋放完讀鎖後會繼而喚醒W4獲取寫鎖,W4在修改完資料釋放寫鎖後,會繼而喚醒R5,R5被喚醒後判斷後繼節點是共享節點會繼喚醒R6,同理R6會喚醒R7,這就是公平模式下讀寫鎖的分配邏輯。

相比於公平模式,非公平模式下的可重入讀寫鎖就有點“蠻不講理”。比如依舊以佇列分佈:head->R2->R3->W4->R5->R6->R7(tail)為列。執行緒1釋放寫鎖喚醒R2,在R2準備請求讀鎖時,執行緒8(W8)搶在R2前佔有了寫鎖,此時R2不能請求讀鎖,只能繼續阻塞。或者當R2和R3釋放讀鎖後喚醒W4,此時執行緒9(R9)搶在W4之前請求到讀鎖,此時W4不能佔有寫鎖,只能繼續阻塞。因此,非公平模式下的讀寫鎖如果出現這種連續不斷的競爭,可能無限期地延遲佇列中的執行緒。但和公平鎖相比,非公平擁有更高的吞吐量,所以ReentrantReadWriteLock預設的無參構造方法使用的是非公平模式,如果要使用公平模式的可重入讀寫鎖,需要使用ReentrantReadWriteLock(boolean fair)建構函式指定公平模式。

需要注意幾點:

  1. 讀鎖(ReadLock)和寫鎖(WriteLock)的tryLock()方法不會遵守公平模式,哪怕我們建立的是公平模式的讀寫鎖,當呼叫tryLock()方法時如果讀寫鎖的條件允許,則會立即佔有讀鎖或寫鎖,不會去管佇列中是否有等待執行緒。
  2. 佔有寫鎖的執行緒可以再獲取讀鎖,在寫鎖釋放後,鎖將從寫鎖降級為讀鎖;但讀鎖不能升級為寫鎖,共享讀鎖的執行緒如果嘗試獲取寫鎖,執行緒會陷入阻塞。

下面正式進入ReentrantReadWriteLock的原始碼解析,我們先看看下ReentrantReadWriteLock的原始碼概覽。先前在講解ReentrantLock和Semaphore相信大家都有看到這兩個類的內部會有個靜態內部類Sync,Sync會繼承AQS類,同時會有公平(FairSync)和非公平(NonfairSync)類繼承Sync,Sync會實現一些較為通用和基礎的的方法,但具體是以公平或者非公平的方式搶鎖由具體的FairSync和NonfairSync類實現。

ReentrantReadWriteLock的實現思路其實也與上面的套路類似,這裡依舊會有個靜態內部類Sync作為公平鎖(FairSync)和非公平鎖(NonfairSync)的父類,除此之外ReentrantReadWriteLock還有兩個靜態內部類讀鎖(ReadLock)和寫鎖(WriteLock),通過這兩個鎖可以讓我們以執行緒安全的方式訪問和修改資源。讀鎖和寫鎖本身也不關心搶鎖的方式是公平或是非公平的方式,這兩個類的內部會維護一個Sync類的內部,由真正的Sync實現決定是以公平/非公平的方式搶鎖。 

public class ReentrantReadWriteLock
        implements ReadWriteLock, java.io.Serializable {
	//...
	private final ReentrantReadWriteLock.ReadLock readerLock;
	private final ReentrantReadWriteLock.WriteLock writerLock;
	final Sync sync;
	public ReentrantReadWriteLock() {
        this(false);
    }
    public ReentrantReadWriteLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
        readerLock = new ReadLock(this);
        writerLock = new WriteLock(this);
    }
    public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; }
    public ReentrantReadWriteLock.ReadLock  readLock()  { return readerLock; }
    abstract static class Sync extends AbstractQueuedSynchronizer {
        static final int SHARED_SHIFT   = 16;
        static final int SHARED_UNIT    = (1 << SHARED_SHIFT);
        static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;
        static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
		static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
		static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
        static final class HoldCounter {
            int count;          // initially 0
            // Use id, not reference, to avoid garbage retention
            final long tid = LockSupport.getThreadId(Thread.currentThread());
        }
        static final class ThreadLocalHoldCounter
            extends ThreadLocal<HoldCounter> {
            public HoldCounter initialValue() {
                return new HoldCounter();
            }
        }
        private transient ThreadLocalHoldCounter readHolds;
        private transient HoldCounter cachedHoldCounter;
        private transient Thread firstReader;
        private transient int firstReaderHoldCount;
        Sync() {
            readHolds = new ThreadLocalHoldCounter();
            setState(getState()); // ensures visibility of readHolds
        }
		//...
	}
	//...
	static final class NonfairSync extends Sync {
		//...
	}
	//...
	static final class FairSync extends Sync {
		//...
	}
	//...
	public static class ReadLock implements Lock, java.io.Serializable {
		//...
		private final Sync sync;
		//...
	}
	//...
    public static class WriteLock implements Lock, java.io.Serializable {
		//...
		private final Sync sync;
		//...
	}
	//...
}

  

觀察上面ReentrantReadWriteLock.Sync類,可以發現這個類相比以前ReentrantLock.Sync和Semaphore.Sync多出很多欄位,我們先來解析這些欄位的用途:

在ReentrantReadWriteLock.Sync中,會用其父類AQS的state欄位來儲存讀鎖和寫鎖的數量,state是int型別(即:4位元組,32位),其中高位16位用於儲存讀鎖的數量,低位16位儲存寫鎖的數量。因此不管是讀鎖和寫鎖,最多被重複獲得65535(MaxUint16)次,不管是以迴圈、遞迴還是重入的方式。那麼我們又是用這個int型別的state欄位讀取和儲存讀鎖和寫鎖的數量呢?

SHARED_SHIFT為16,代表讀鎖和寫鎖在state欄位裡的分界線,SHARED_UNIT是往state欄位增加一個讀執行緒的單位數量,比如我們連續向state新增兩個讀執行緒:

#初始state為0,其二進位制表示為32個0,加上SHARED_UNIT,高位16位表示當前讀執行緒數量為1
	0000 0000 0000 0000 0000 0000 0000 0000
+	0000 0000 0000 0001 0000 0000 0000 0000
——————————————————————————————————————————————
#此時又有新的讀執行緒共享讀鎖,所以state加上SHARED_UNIT,高位16位表示當前讀執行緒數量為2
	0000 0000 0000 0001 0000 0000 0000 0000
+	0000 0000 0000 0001 0000 0000 0000 0000
——————————————————————————————————————————————
	0000 0000 0000 0010 0000 0000 0000 0000

  

現在state的值為:0000 0000 0000 0010 0000 0000 0000 0000,我們要如何從這個欄位得到讀執行緒的數量呢?我們可以將state傳入到sharedCount(int c)算出讀執行緒的數量,這個方法會對state無符號右移(也叫邏輯右移)16(SHARED_SHIFT)位,不管最高位是0還是1,右移後的前16位都會補0。我們根據現有的讀執行緒數為2的state無符號右移16位:0000 0000 0000 0010 0000 0000 0000 0000 >>> 16 = 0000 0000 0000 0000 0000 0000 0000 0010,算出的結果剛好為2,也就是讀執行緒的數量。所以,如果我們要增減一個讀執行緒數,只要對state加減一個SHARED_UNIT,當需要從state獲取當前的讀執行緒數,只要將state無符號右移16位即可。

MAX_COUNT和EXCLUSIVE_MASK的結果一樣,都為(2^16)-1=65535,其二進位制表示為:0000 0000 0000 0000 1111 1111 1111 1111。但兩者的使用場景卻不同,之前說過讀鎖和寫鎖會多獲取65535次,這裡會用MAX_COUNT來做校驗。EXCLUSIVE_MASK是用來計算寫鎖被重入次數,假設執行緒有一執行緒在重入兩次寫鎖後又獲取一次讀鎖,其state的二進位制表示為:0000 0000 0000 0001 0000 0000 0000 0010,我們將state傳入到exclusiveCount(int c)可以算出寫鎖的被重入次數,這個方法對將state和EXCLUSIVE_MASK做&運算,EXCLUSIVE_MASK的高16位都為0,做&運算其結果的高16位會清零,去除讀鎖的數量,EXCLUSIVE_MASK的低16位都為1,做&運算後能保留state低16位的結果。

#執行緒重入兩次寫鎖後獲取一次讀鎖,與EXCLUSIVE_MASK做&運算得到寫鎖被重入次數2
	0000 0000 0000 0001 0000 0000 0000 0010
&	0000 0000 0000 0000 1111 1111 1111 1111
——————————————————————————————————————————————
	0000 0000 0000 0000 0000 0000 0000 0010

  

readHolds的型別為ThreadLocalHoldCounter,其父類是ThreadLocal,ThreadLocalHoldCounter重寫了父類ThreadLocal的initialValue()方法,當讀執行緒第一次呼叫readHolds.get()方法時會呼叫ThreadLocalHoldCounter.initialValue()方法,為獲取讀鎖的執行緒生成一個執行緒區域性變數HoldCounter物件,用於統計執行緒重入讀鎖的次數。

cachedHoldCounter、firstReader、firstReaderHoldCount用於更快地計算讀執行緒獲取讀鎖的次數,cachedHoldCounter用於指向上一個獲取讀鎖的執行緒的HoldCounter物件,當有讀執行緒獲取讀鎖時,會先判斷執行緒id是否與當前cachedHoldCounter的執行緒id相同,如果相同則對其獲取次數count+1,firstReader用於指向第一個獲取讀鎖的執行緒,第一個獲取讀鎖的執行緒也不需要通過readHolds來生成執行緒區域性變數HoldCounter物件,可以直接用firstReaderHoldCount來統計讀鎖的獲取次數。當然,釋放讀鎖的時候,也會對讀鎖的持有次數-1。

我們已經對ReentrantReadWriteLock的靜態內部類Sync有了基本的認識,下面我們來看看寫鎖ReentrantReadWriteLock.WriteLock的實現。

我們先從lock()方法開始介紹,當我們呼叫WriteLock.lock()方法,會呼叫Sync父類AQS實現的acquire(int arg),這個方法會先呼叫子類實現的tryAcquire(int arg)嘗試獲取寫鎖,如果獲取失敗,在呼叫acquireQueued(addWaiter(Node.EXCLUSIVE), arg)方法將需要獲取寫鎖的執行緒掛起,關於acquireQueued(addWaiter(Node.EXCLUSIVE), arg)的實現大家可以去ReentrantLock原始碼解析再複習一遍,筆者不再贅述,這裡主要解釋tryAcquire(int acquires)的實現是如何嘗試獲取鎖的。

首先在<1>處獲取讀寫鎖當前的狀態c,之後獲取寫鎖的數量w,如果當前狀態c不為0,代表有執行緒佔有讀鎖或者寫鎖,則進入<3>處的分支。在讀寫鎖狀態不為0的情況寫,如果獲取寫鎖數量w為0,代表現在有執行緒獲取到讀鎖,嘗試獲取寫鎖失敗;又或者w不為0,代表當前有執行緒佔有寫鎖,但當前執行緒不是獨佔寫鎖的執行緒,嘗試獲取寫鎖失敗,這兩種情況都會進入<4>處的分支。

如果<4>處的判斷結果為false,代表寫鎖被重入,即執行緒重複獲取寫鎖,這裡會先判斷原先獲取寫鎖的次數加上要獲取的次數acquires,如果總獲取次數超過MAX_COUNT(即:65535,MaxUint16)則報錯。否則將最新的寫鎖獲取次數c+acquires儲存進父類AQS的state欄位裡。

如果在<1>處獲取到的讀寫鎖狀態為0,代表當先沒有讀執行緒或者寫執行緒佔有讀寫鎖,這裡會先呼叫writerShouldBlock()判斷是否應該阻塞當前寫執行緒,Sync並沒有實現writerShouldBlock()方法,而是交由它的子類公平鎖(FairSync)和非公平鎖(NonfairSync)實現,由具體的實現決定此處是否以公平還是非公平的方式搶鎖,如果是公平模式,則writerShouldBlock()的實現只要簡單判斷下等待佇列中是否有執行緒,有執行緒則不搶鎖,如果非公平模式,就會返回false,然後在<6>處嘗試搶鎖。假如writerShouldBlock()返回false,在<6>處會以CAS的方式嘗試更新讀寫鎖的狀態為c + acquires,如果更新成功則代表執行緒成功獲取寫鎖,會設定寫鎖的獨佔執行緒為當前執行緒並返回搶鎖成功;如果CAS失敗代表當前有執行緒獲取到讀鎖或者寫鎖,會進入<6>處的分支返回搶鎖失敗。

public class ReentrantReadWriteLock
        implements ReadWriteLock, java.io.Serializable {
	//...
    abstract static class Sync extends AbstractQueuedSynchronizer {
		//...
        protected final boolean tryAcquire(int acquires) {
            Thread current = Thread.currentThread();
            int c = getState();//<1>
            int w = exclusiveCount(c);//<2>
            if (c != 0) {//<3>
                // (Note: if c != 0 and w == 0 then shared count != 0)
                if (w == 0 || current != getExclusiveOwnerThread())//<4>
                    return false;
                if (w + exclusiveCount(acquires) > MAX_COUNT)//<5>
                    throw new Error("Maximum lock count exceeded");
                // Reentrant acquire
                setState(c + acquires);
                return true;
            }
            if (writerShouldBlock() ||
                !compareAndSetState(c, c + acquires))//<6>
                return false;
            setExclusiveOwnerThread(current);
            return true;
        }
		//...
        abstract boolean writerShouldBlock();
		//...
	}
	//...
    public static class WriteLock implements Lock, java.io.Serializable {
		//...
        public void lock() {
            sync.acquire(1);
        }
		//...
	}
	//...
}
public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {
	//...
    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
	//...
    protected boolean tryAcquire(int arg) {
        throw new UnsupportedOperationException();
    }
	//...
}

  

這裡我們看到Sync抽象方法writerShouldBlock()在公平鎖和非公平鎖的實現,公平鎖會判斷等待隊裡是否有執行緒,有的話則返回true,上面的程式碼會進入<6>處的分支返回搶鎖失敗,如果是非公平鎖的writerShouldBlock()直接返回false,在<6>處會嘗試用CAS修改寫鎖的獲取次數。

static final class FairSync extends Sync {
	final boolean writerShouldBlock() {
		return hasQueuedPredecessors();
	}
	//...
}

static final class NonfairSync extends Sync {
	final boolean writerShouldBlock() {
		return false; // writers can always barge
	}
	//...
}

  

當呼叫寫鎖的unlock()方法時,會繼而呼叫到AQS的release(int arg)方法,這個方法會先呼叫由子類Sync實現的tryRelease(int releases)方法嘗試解鎖,如果嘗試解鎖的執行緒不是獨佔寫鎖的執行緒,這裡會丟擲IllegalMonitorStateException異常,如果當前讀寫鎖的狀態減去釋放寫鎖的數量(state-releases)算出的獲取寫鎖數量nextc為0,代表執行緒要完全釋放寫鎖,這裡會先置空獨佔執行緒,在設定state為nextc,需要注意的是:可能存在獲取寫鎖後又獲取讀鎖的情況,所以這裡在釋放寫鎖後不能直接將state清零,要考慮到state的前16位可能存在被當前執行緒持有讀鎖的情況。如果能完全釋放寫鎖,會再呼叫unparkSuccessor(h)嘗試喚醒頭節點的後繼節點起來申請讀寫鎖。

public class ReentrantReadWriteLock
        implements ReadWriteLock, java.io.Serializable {
	//...	
	abstract static class Sync extends AbstractQueuedSynchronizer {
		//...	
        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;
        }
		//...	
	}
    public static class WriteLock implements Lock, java.io.Serializable {
		//...
        public void unlock() {
            sync.release(1);
        }
		//...
	}
	//...
}

public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {
	//...
    public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
	//...
    protected boolean tryRelease(int arg) {
        throw new UnsupportedOperationException();
    }
	//...
}

  

寫鎖的tryLock()沒有公平和非公平之分,不管是公平鎖還是非公平鎖,會呼叫Sync類實現的tryWriteLock()方法統一以非公平的方式嘗試搶鎖,其實現思路也和上面的lock()十分相近,先獲取讀寫鎖當前狀態c,如果c不為0則代表當前有執行緒獲取到讀鎖或者寫鎖,會進入<1>處的分支判斷當前是否是寫鎖被佔用,如果佔有寫鎖的數量為0則代表有執行緒獲取到讀鎖,這裡會直接返回搶鎖失敗,如果w不為0,則代表有執行緒獲取到寫鎖,會再接著判斷獲取寫鎖的執行緒是否是當前執行緒,如果不是則返回搶鎖失敗。如果獲取寫鎖的數量不為0且佔有寫鎖的執行緒為當前執行緒,會再接著判斷當前寫鎖的可重入次數是否已達到MAX_COUNT,是的話則丟擲異常。如果當前沒有執行緒佔有讀寫鎖,或者執行tryLock()方法的執行緒是已經佔有寫鎖的執行緒,這裡會執行到<2>處的分支用CAS的方式修改獲取寫鎖的次數,如果state原先為0,這裡可能會和其他執行緒搶鎖導致失敗,如果是原先就佔有寫鎖的執行緒執行到<2>處是沒有併發問題的。如果能CAS成功,則設定獨佔執行緒為當前執行緒。

public class ReentrantReadWriteLock
        implements ReadWriteLock, java.io.Serializable {
	//...
    abstract static class Sync extends AbstractQueuedSynchronizer {
		//...
        final boolean tryWriteLock() {
            Thread current = Thread.currentThread();
            int c = getState();
            if (c != 0) {//<1>
                int w = exclusiveCount(c);
                if (w == 0 || current != getExclusiveOwnerThread())
                    return false;
                if (w == MAX_COUNT)
                    throw new Error("Maximum lock count exceeded");
            }
            if (!compareAndSetState(c, c + 1))//<2>
                return false;
            setExclusiveOwnerThread(current);
            return true;
        }
		//...
	}
	//...
    public static class WriteLock implements Lock, java.io.Serializable {
		//...
        public boolean tryLock() {
            return sync.tryWriteLock();
        }
		//...
	}
}

  

下面,我們簡單來看下寫鎖的tryLock(long timeout, TimeUnit unit),這裡會呼叫Sync父類AQS實現的tryAcquireNanos(int arg, long nanosTimeout),tryAcquireNanos(int arg, long nanosTimeout)內部的實現其實基本上都講過,比如:tryAcquire(arg)和doAcquireNanos(arg, nanosTimeout),這裡就不再贅述了。

public class ReentrantReadWriteLock
        implements ReadWriteLock, java.io.Serializable {
	//...
    public static class WriteLock implements Lock, java.io.Serializable {
		//...
        public boolean tryLock(long timeout, TimeUnit unit)
                throws InterruptedException {
            return sync.tryAcquireNanos(1, unit.toNanos(timeout));
        }
		//...
	}
}

public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {
	//...
    public final boolean tryAcquireNanos(int arg, long nanosTimeout)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        return tryAcquire(arg) ||
            doAcquireNanos(arg, nanosTimeout);
    }
	//...
}

  

相關文章