【讀寫鎖】ReadWriteLock
ReadWriteLock管理一組鎖,一個是隻讀的鎖,一個是寫鎖。讀鎖可以在沒有寫鎖的時候被多個執行緒同時持有,寫鎖是獨佔的。 所有讀寫鎖的實現必須確保寫操作對讀操作的記憶體影響。換句話說,一個獲得了讀鎖的執行緒必須能看到前一個釋放的寫鎖所更新的內容。 讀寫鎖比互斥鎖允許對於共享資料更大程度的併發。每次只能有一個寫執行緒,但是同時可以有多個執行緒併發地讀資料。ReadWriteLock適用於讀多寫少的併發情況。 Java併發包中ReadWriteLock是一個介面,主要有兩個方法,如下:
public interface ReadWriteLock { /** * 返回讀鎖 */ Lock readLock();
/** * 返回寫鎖 */ Lock writeLock(); } 1 2 3 4 5 6 7 8 9 10 11 Java併發庫中ReetrantReadWriteLock實現了ReadWriteLock介面並添加了可重入的特性。
ReentrantReadWriteLock分析 特性 ReentrantReadWriteLock有如下特性: - 獲取順序 - 非公平模式(預設) 當以非公平初始化時,讀鎖和寫鎖的獲取的順序是不確定的。非公平鎖主張競爭獲取,可能會延緩一個或多個讀或寫執行緒,但是會比公平鎖有更高的吞吐量。 - 公平模式 當以公平模式初始化時,執行緒將會以佇列的順序獲取鎖。噹噹前執行緒釋放鎖後,等待時間最長的寫鎖執行緒就會被分配寫鎖;或者有一組讀執行緒組等待時間比寫執行緒長,那麼這組讀執行緒組將會被分配讀鎖。 當有寫執行緒持有寫鎖或者有等待的寫執行緒時,一個嘗試獲取公平的讀鎖(非重入)的執行緒就會阻塞。這個執行緒直到等待時間最長的寫鎖獲得鎖後並釋放掉鎖後才能獲取到讀鎖。 - 可重入 允許讀鎖可寫鎖可重入。寫鎖可以獲得讀鎖,讀鎖不能獲得寫鎖。 - 鎖降級 允許寫鎖降低為讀鎖 - 中斷鎖的獲取 在讀鎖和寫鎖的獲取過程中支援中斷 - 支援Condition 寫鎖提供Condition實現 - 監控 提供確定鎖是否被持有等輔助方法
使用 下面一段程式碼展示了鎖降低的操作:
class CachedData { Object data; volatile boolean cacheValid; final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
void processCachedData() { rwl.readLock().lock(); if (!cacheValid) { // Must release read lock before acquiring write lock rwl.readLock().unlock(); rwl.writeLock().lock(); try { // Recheck state because another thread might have // acquired write lock and changed state before we did. if (!cacheValid) { data = ... cacheValid = true; } // Downgrade by acquiring read lock before releasing write lock rwl.readLock().lock(); } finally { rwl.writeLock().unlock(); // Unlock write, still hold read } }
try { use(data); } finally { rwl.readLock().unlock(); } } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 ReentrantReadWriteLock可以用來提高某些集合的併發效能。當集合比較大,並且讀比寫頻繁時,可以使用該類。下面是TreeMap使用ReentrantReadWriteLock進行封裝成併發效能提高的一個例子:
class RWDictionary { private final Map<String, Data> m = new TreeMap<String, Data>(); private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); private final Lock r = rwl.readLock(); private final Lock w = rwl.writeLock();
public Data get(String key) { r.lock(); try { return m.get(key); } finally { r.unlock(); } } public String[] allKeys() { r.lock(); try { return m.keySet().toArray(); } finally { r.unlock(); } } public Data put(String key, Data value) { w.lock(); try { return m.put(key, value); } finally { w.unlock(); } } public void clear() { w.lock(); try { m.clear(); } finally { w.unlock(); } } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 原始碼分析 構造方法 ReentrantReadWriteLock有兩個構造方法,如下:
public ReentrantReadWriteLock() { this(false); }
public ReentrantReadWriteLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); readerLock = new ReadLock(this); writerLock = new WriteLock(this); } 1 2 3 4 5 6 7 8 9 10 可以看到,預設的構造方法使用的是非公平模式,建立的Sync是NonfairSync物件,然後初始化讀鎖和寫鎖。一旦初始化後,ReadWriteLock介面中的兩個方法就有返回值了,如下:
public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; } public ReentrantReadWriteLock.ReadLock readLock() { return readerLock; } 1 2 從上面可以看到,構造方法決定了Sync是FairSync還是NonfairSync。Sync繼承了AbstractQueuedSynchronizer,而Sync是一個抽象類,NonfairSync和FairSync繼承了Sync,並重寫了其中的抽象方法。
Sync分析 Sync中提供了很多方法,但是有兩個方法是抽象的,子類必須實現。下面以FairSync為例,分析一下這兩個抽象方法:
static final class FairSync extends Sync { private static final long serialVersionUID = -2274990926593161451L; final boolean writerShouldBlock() { return hasQueuedPredecessors(); } final boolean readerShouldBlock() { return hasQueuedPredecessors(); } } 1 2 3 4 5 6 7 8 9 writerShouldBlock和readerShouldBlock方法都表示當有別的執行緒也在嘗試獲取鎖時,是否應該阻塞。 對於公平模式,hasQueuedPredecessors()方法表示前面是否有等待執行緒。一旦前面有等待執行緒,那麼為了遵循公平,當前執行緒也就應該被掛起。 下面再來看NonfairSync的實現:
static final class NonfairSync extends Sync { private static final long serialVersionUID = -8159625535654395037L; final boolean writerShouldBlock() { return false; // writers can always barge } 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(); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 從上面可以看到,非公平模式下,writerShouldBlock直接返回false,說明不需要阻塞;而readShouldBlock呼叫了apparentFirstQueuedIsExcluisve()方法。該方法在當前執行緒是寫鎖佔用的執行緒時,返回true;否則返回false。也就說明,如果當前有一個寫執行緒正在寫,那麼該讀執行緒應該阻塞。 繼承AQS的類都需要使用state變數代表某種資源,ReentrantReadWriteLock中的state代表了讀鎖的數量和寫鎖的持有與否,整個結構如下:
可以看到state的高16位代表讀鎖的個數;低16位代表寫鎖的狀態。
獲取鎖 讀鎖的獲取 當需要使用讀鎖時,首先呼叫lock方法,如下:
public void lock() { sync.acquireShared(1); } 1 2 3 從程式碼可以看到,讀鎖使用的是AQS的共享模式,AQS的acquireShared方法如下:
if (tryAcquireShared(arg) < 0) doAcquireShared(arg); 1 2 當tryAcquireShared()方法小於0時,那麼會執行doAcquireShared方法將該執行緒加入到等待佇列中。 Sync實現了tryAcquireShared方法,如下:
protected final int tryAcquireShared(int unused) { /* * Walkthrough: * 1. If write lock held by another thread, fail. * 2. Otherwise, this thread is eligible for * lock wrt state, so ask if it should block * because of queue policy. If not, try * to grant by CASing state and updating count. * Note that step does not check for reentrant * acquires, which is postponed to full version * to avoid having to check hold count in * the more typical non-reentrant case. * 3. If step 2 fails either because thread * apparently not eligible or CAS fails or count * saturated, chain to version with full retry loop. */ Thread current = Thread.currentThread(); int c = getState(); //如果當前有寫執行緒並且本執行緒不是寫執行緒,不符合重入,失敗 if (exclusiveCount(c) != 0 && getExclusiveOwnerThread() != current) return -1; //得到讀鎖的個數 int r = sharedCount(c); //如果讀不應該阻塞並且讀鎖的個數小於最大值65535,並且可以成功更新狀態值,成功 if (!readerShouldBlock() && r < MAX_COUNT && compareAndSetState(c, c + SHARED_UNIT)) { //如果當前讀鎖為0 if (r == 0) { //第一個讀執行緒就是當前執行緒 firstReader = current; firstReaderHoldCount = 1; } //如果當前執行緒重入了,記錄firstReaderHoldCount else if (firstReader == current) { firstReaderHoldCount++; } //當前讀執行緒和第一個讀執行緒不同,記錄每一個執行緒讀的次數 else { HoldCounter rh = cachedHoldCounter; if (rh == null || rh.tid != getThreadId(current)) cachedHoldCounter = rh = readHolds.get(); else if (rh.count == 0) readHolds.set(rh); rh.count++; } return 1; } //否則,迴圈嘗試 return fullTryAcquireShared(current); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 從上面的程式碼以及註釋可以看到,分為三步: 1. 如果當前有寫執行緒並且本執行緒不是寫執行緒,那麼失敗,返回-1 2. 否則,說明當前沒有寫執行緒或者本執行緒就是寫執行緒(可重入),接下來判斷是否應該讀執行緒阻塞並且讀鎖的個數是否小於最小值,並且CAS成功使讀鎖+1,成功,返回1。其餘的操作主要是用於計數的 3. 如果2中失敗了,失敗的原因有三,第一是應該讀執行緒應該阻塞;第二是因為讀鎖達到了上線;第三是因為CAS失敗,有其他執行緒在併發更新state,那麼會調動fullTryAcquireShared方法。
fullTryAcquiredShared方法如下:
final int fullTryAcquireShared(Thread current) {
HoldCounter rh = null; for (;;) { int c = getState(); //一旦有別的執行緒獲得了寫鎖,返回-1,失敗 if (exclusiveCount(c) != 0) { if (getExclusiveOwnerThread() != current) return -1; } //如果讀執行緒需要阻塞 else if (readerShouldBlock()) { // Make sure we're not acquiring read lock reentrantly if (firstReader == current) { // assert firstReaderHoldCount > 0; } //說明有別的讀執行緒佔有了鎖 else { if (rh == null) { rh = cachedHoldCounter; if (rh == null || rh.tid != getThreadId(current)) { rh = readHolds.get(); if (rh.count == 0) readHolds.remove(); } } if (rh.count == 0) return -1; } } //如果讀鎖達到了最大值,丟擲異常 if (sharedCount(c) == MAX_COUNT) throw new Error("Maximum lock count exceeded"); //如果成功更改狀態,成功返回 if (compareAndSetState(c, c + SHARED_UNIT)) { if (sharedCount(c) == 0) { firstReader = current; firstReaderHoldCount = 1; } else if (firstReader == current) { firstReaderHoldCount++; } else { if (rh == null) rh = cachedHoldCounter; if (rh == null || rh.tid != getThreadId(current)) rh = readHolds.get(); else if (rh.count == 0) readHolds.set(rh); rh.count++; cachedHoldCounter = rh; // cache for release } return 1; } } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 從上面可以看到fullTryAcquireShared與tryAcquireShared有很多類似的地方。 在上面可以看到多次呼叫了readerShouldBlock方法,對於公平鎖,只要佇列中有執行緒在等待,那麼將會返回true,也就意味著讀執行緒需要阻塞;對於非公平鎖,如果當前有執行緒獲取了寫鎖,則返回true。一旦不阻塞,那麼讀執行緒將會有機會獲得讀鎖。
寫鎖的獲取 寫鎖的lock方法如下:
public void lock() { sync.acquire(1); } 1 2 3 AQS的acquire方法如下:
public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); } 1 2 3 4 5 從上面可以看到,寫鎖使用的是AQS的獨佔模式。首先嚐試獲取鎖,如果獲取失敗,那麼將會把該執行緒加入到等待佇列中。 Sync實現了tryAcquire方法用於嘗試獲取一把鎖,如下:
protected final boolean tryAcquire(int acquires) { /* * Walkthrough: * 1. If read count nonzero or write count nonzero * and owner is a different thread, fail. * 2. If count would saturate, fail. (This can only * happen if count is already nonzero.) * 3. Otherwise, this thread is eligible for lock if * it is either a reentrant acquire or * queue policy allows it. If so, update state * and set owner. */ //得到呼叫lock方法的當前執行緒 Thread current = Thread.currentThread(); int c = getState(); //得到寫鎖的個數 int w = exclusiveCount(c); //如果當前有寫鎖或者讀鎖 if (c != 0) { // 如果寫鎖為0或者當前執行緒不是獨佔執行緒(不符合重入),返回false if (w == 0 || current != getExclusiveOwnerThread()) return false; //如果寫鎖的個數超過了最大值,丟擲異常 if (w + exclusiveCount(acquires) > MAX_COUNT) throw new Error("Maximum lock count exceeded"); // 寫鎖重入,返回true setState(c + acquires); return true; } //如果當前沒有寫鎖或者讀鎖,如果寫執行緒應該阻塞或者CAS失敗,返回false if (writerShouldBlock() || !compareAndSetState(c, c + acquires)) return false; //否則將當前執行緒置為獲得寫鎖的執行緒,返回true setExclusiveOwnerThread(current); return true; } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 從程式碼和註釋可以看到,獲取寫鎖時有三步: 1. 如果當前有寫鎖或者讀鎖。如果只有讀鎖,返回false,因為這時如果可以寫,那麼讀執行緒得到的資料就有可能錯誤;如果有寫鎖,但是執行緒不同,即不符合寫鎖重入規則,返回false 2. 如果寫鎖的數量將會超過最大值65535,丟擲異常;否則,寫鎖重入 3. 如果沒有讀鎖或寫鎖的話,如果需要阻塞或者CAS失敗,返回false;否則將當前執行緒置為獲得寫鎖的執行緒
從上面可以看到呼叫了writerShouldBlock方法,FairSync的實現是如果等待佇列中有等待執行緒,則返回false,說明公平模式下,只要佇列中有執行緒在等待,那麼後來的這個執行緒也是需要記入佇列等待的;NonfairSync中的直接返回的直接是false,說明不需要阻塞。從上面的程式碼可以得出,當沒有鎖時,如果使用的非公平模式下的寫鎖的話,那麼返回false,直接通過CAS就可以獲得寫鎖。
總結 從上面分析可以得出結論: - 如果當前沒有寫鎖或讀鎖時,第一個獲取鎖的執行緒都會成功,無論該鎖是寫鎖還是讀鎖。 - 如果當前已經有了讀鎖,那麼這時獲取寫鎖將失敗,獲取讀鎖有可能成功也有可能失敗 - 如果當前已經有了寫鎖,那麼這時獲取讀鎖或寫鎖,如果執行緒相同(可重入),那麼成功;否則失敗
釋放鎖 獲取鎖要做的是更改AQS的狀態值以及將需要等待的執行緒放入到佇列中;釋放鎖要做的就是更改AQS的狀態值以及喚醒佇列中的等待執行緒來繼續獲取鎖。
讀鎖的釋放 ReadLock的unlock方法如下:
public void unlock() { sync.releaseShared(1); } 1 2 3 呼叫了Sync的releaseShared方法,該方法在AQS中提供,如下:
public final boolean releaseShared(int arg) { if (tryReleaseShared(arg)) { doReleaseShared(); return true; } return false; } 1 2 3 4 5 6 7 呼叫tryReleaseShared方法嘗試釋放鎖,如果釋放成功,呼叫doReleaseShared嘗試喚醒下一個節點。 AQS的子類需要實現tryReleaseShared方法,Sync中的實現如下:
protected final boolean tryReleaseShared(int unused) { //得到呼叫unlock的執行緒 Thread current = Thread.currentThread(); //如果是第一個獲得讀鎖的執行緒 if (firstReader == current) { // assert firstReaderHoldCount > 0; if (firstReaderHoldCount == 1) firstReader = null; else firstReaderHoldCount--; } //否則,是HoldCounter中計數-1 else { HoldCounter rh = cachedHoldCounter; if (rh == null || rh.tid != getThreadId(current)) rh = readHolds.get(); int count = rh.count; if (count <= 1) { readHolds.remove(); if (count <= 0) throw unmatchedUnlockException(); } --rh.count; } //死迴圈 for (;;) { int c = getState(); //釋放一把讀鎖 int nextc = c - SHARED_UNIT; //如果CAS更新狀態成功,返回讀鎖是否等於0;失敗的話,則重試 if (compareAndSetState(c, nextc)) // Releasing the read lock has no effect on readers, // but it may allow waiting writers to proceed if // both read and write locks are now free. return nextc == 0; } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 從上面可以看到,釋放鎖的第一步是更新firstReader或HoldCounter的計數,接下來進入死迴圈,嘗試更新AQS的狀態,一旦更新成功,則返回;否則,則重試。 釋放讀鎖對讀執行緒沒有影響,但是可能會使等待的寫執行緒解除掛起開始執行。所以,一旦沒有鎖了,就返回true,否則false;返回true後,那麼則需要釋放等待佇列中的執行緒,這時讀執行緒和寫執行緒都有可能再獲得鎖。
寫鎖的釋放 WriteLock的unlock方法如下:
public void unlock() { sync.release(1); } 1 2 3 Sync的release方法使用的AQS中的,如下:
public final boolean release(int arg) { if (tryRelease(arg)) { Node h = head; if (h != null && h.waitStatus != 0) unparkSuccessor(h); return true; } return false; } 1 2 3 4 5 6 7 8 9 呼叫tryRelease嘗試釋放鎖,一旦釋放成功了,那麼如果等待佇列中有執行緒再等待,那麼呼叫unparkSuccessor將下一個執行緒解除掛起。 Sync需要實現tryRelease方法,如下:
protected final boolean tryRelease(int releases) { //如果沒有執行緒持有寫鎖,但是仍要釋放,丟擲異常 if (!isHeldExclusively()) throw new IllegalMonitorStateException(); int nextc = getState() - releases; boolean free = exclusiveCount(nextc) == 0; //如果沒有寫鎖了,那麼將AQS的執行緒置為null if (free) setExclusiveOwnerThread(null); //更新狀態 setState(nextc); return free; } 1 2 3 4 5 6 7 8 9 10 11 12 13 從上面可以看到,寫鎖的釋放主要有三步: 1. 如果當前沒有執行緒持有寫鎖,但是還要釋放寫鎖,丟擲異常 2. 得到解除一把寫鎖後的狀態,如果沒有寫鎖了,那麼將AQS的執行緒置為null 3. 不管第二步中是否需要將AQS的執行緒置為null,AQS的狀態總是要更新的
從上面可以看到,返回true當且只當沒有寫鎖的情況下,還有寫鎖則返回false。
總結 從上面的分析可以得出: - 如果當前是寫鎖被佔有了,只有當寫鎖的資料降為0時才認為釋放成功;否則失敗。因為只要有寫鎖,那麼除了佔有寫鎖的那個執行緒,其他執行緒即不可以獲得讀鎖,也不能獲得寫鎖 - 如果當前是讀鎖被佔有了,那麼只有在寫鎖的個數為0時才認為釋放成功。因為一旦有寫鎖,別的任何執行緒都不應該再獲得讀鎖了,除了獲得寫鎖的那個執行緒。
其他方法 看完了ReentrantReadWriteLock中的讀鎖的獲取和釋放,寫鎖的獲取和釋放,再來看一下其餘的一些輔助方法來加深我們對讀寫鎖的理解。
getOwner() getOwner方法用於返回當前獲得寫鎖的執行緒,如果沒有執行緒佔有寫鎖,那麼返回null。實現如下:
protected Thread getOwner() { return sync.getOwner(); } 1 2 3 可以看到直接呼叫了Sync的getOwner方法,下面是Sync的getOwner方法:
final Thread getOwner() { // Must read state before owner to ensure memory consistency return ((exclusiveCount(getState()) == 0) ? null : getExclusiveOwnerThread()); } 1 2 3 4 5 6 如果獨佔鎖的個數為0,說明沒有執行緒佔有寫鎖,那麼返回null;否則返回佔有寫鎖的執行緒。
getReadLockCount() getReadLockCount()方法用於返回讀鎖的個數,實現如下:
public int getReadLockCount() { return sync.getReadLockCount(); } 1 2 3 Sync的實現如下:
final int getReadLockCount() { return sharedCount(getState()); }
static int sharedCount(int c) { return c >>> SHARED_SHIFT; } 1 2 3 4 5 6 從上面程式碼可以看出,要想得到讀鎖的個數,就是看AQS的state的高16位。這和前面講過的一樣,高16位表示讀鎖的個數,低16位表示寫鎖的個數。
getReadHoldCount() getReadHoldCount()方法用於返回當前執行緒所持有的讀鎖的個數,如果當前執行緒沒有持有讀鎖,則返回0。直接看Sync的實現即可:
final int getReadHoldCount() { //如果沒有讀鎖,自然每個執行緒都是返回0 if (getReadLockCount() == 0) return 0;
//得到當前執行緒 Thread current = Thread.currentThread(); //如果當前執行緒是第一個讀執行緒,返回firstReaderHoldCount引數 if (firstReader == current) return firstReaderHoldCount; //如果當前執行緒不是第一個讀執行緒,得到HoldCounter,返回其中的count HoldCounter rh = cachedHoldCounter; //如果快取的HoldCounter不為null並且是當前執行緒的HoldCounter,直接返回count if (rh != null && rh.tid == getThreadId(current)) return rh.count;
//如果快取的HoldCounter不是當前執行緒的HoldCounter,那麼從ThreadLocal中得到本執行緒的HoldCounter,返回計數 int count = readHolds.get().count; //如果本執行緒持有的讀鎖為0,從ThreadLocal中移除 if (count == 0) readHolds.remove(); return count; } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 從上面的程式碼中,可以看到兩個熟悉的變數,firstReader和HoldCounter型別。這兩個變數在讀鎖的獲取中接觸過,前面沒有細說,這裡細說一下。HoldCounter類的實現如下:
static final class HoldCounter { int count = 0; // Use id, not reference, to avoid garbage retention final long tid = getThreadId(Thread.currentThread()); } 1 2 3 4 5 readHolds是ThreadLocalHoldCounter類,定義如下:
static final class ThreadLocalHoldCounter extends ThreadLocal<HoldCounter> { public HoldCounter initialValue() { return new HoldCounter(); } } 1 2 3 4 5 6 可以看到,readHolds儲存了每一個執行緒的HoldCounter,而HoldCounter中的count變數就是用來記錄執行緒獲得的寫鎖的個數。所以可以得出結論:Sync維持總的讀鎖的個數,在state的高16位;由於讀執行緒可以同時存在,所以每個執行緒還儲存了獲得的讀鎖的個數,這個是通過HoldCounter來儲存的。 除此之外,對於第一個讀執行緒有特殊的處理,Sync中有如下兩個變數:
private transient Thread firstReader = null; private transient int firstReaderHoldCount; 1 2 firstReader表示第一個得到讀鎖的執行緒,firstReaderHoldCount表示這個執行緒獲得的寫鎖。所以可以得出結論:第一個獲取到讀鎖的資訊儲存在firstReader中;其餘獲取到讀鎖的執行緒的資訊儲存在HoldCounter中。 看完了HoldCounter和firstReader,再來看一下getReadLockCount的實現,主要有三步: 1. 當前沒有讀鎖,那麼自然每一個執行緒獲得的讀鎖都是0; 2. 如果當前執行緒是第一個獲取到讀鎖的執行緒,那麼返回firstReadHoldCount; 3. 如果當前執行緒不是第一個獲取到讀鎖的執行緒,得到該執行緒的HoldCounter,然後返回其count欄位。如果count欄位為0,說明該執行緒沒有佔有讀鎖,那麼從readHolds中移除。獲取HoldCounter分為兩步,第一步是與cachedHoldCounter比較,如果不是,則從readHolds中獲取。
getWriteLockCount() getWriteLockCount()方法返回寫鎖的個數,Sync的實現如下:
final int getWriteHoldCount() { return isHeldExclusively() ? exclusiveCount(getState()) : 0; } 1 2 3 可以看到如果沒有執行緒持有寫鎖,那麼返回0;否則返回AQS的state的低16位。
總結 當分析ReentranctReadWriteLock時,或者說分析內部使用AQS實現的工具類時,需要明白的就是AQS的state代表的是什麼。ReentrantLockReadWriteLock中的state同時表示寫鎖和讀鎖的個數。為了實現這種功能,state的高16位表示讀鎖的個數,低16位表示寫鎖的個數。AQS有兩種模式:共享模式和獨佔模式,讀寫鎖的實現中,讀鎖使用共享模式;寫鎖使用獨佔模式;另外一點需要記住的即使,當有讀鎖時,寫鎖就不能獲得;而當有寫鎖時,除了獲得寫鎖的這個執行緒可以獲得讀鎖外,其他執行緒不能獲得讀鎖。 --------------------- 轉自:https://blog.csdn.net/qq_19431333/article/details/70568478