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)構造函數指定公平模式。
需要注意幾點:
- 讀鎖(ReadLock)和寫鎖(WriteLock)的tryLock()方法不會遵守公平模式,哪怕我們創建的是公平模式的讀寫鎖,當調用tryLock()方法時如果讀寫鎖的條件允許,則會立即占有讀鎖或寫鎖,不會去管隊列中是否有等待線程。
- 占有寫鎖的線程可以再獲取讀鎖,在寫鎖釋放后,鎖將從寫鎖降級為讀鎖;但讀鎖不能升級為寫鎖,共享讀鎖的線程如果嘗試獲取寫鎖,線程會陷入阻塞。
下面正式進入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); } //... }