1. 程式人生 > >原始碼分析:ReentrantReadWriteLock之讀寫鎖

原始碼分析:ReentrantReadWriteLock之讀寫鎖

## 簡介 ReentrantReadWriteLock 從字面意思可以看出,是和重入、讀寫有關係的鎖,實際上 ReentrantReadWriteLock 確實也是支援可重入的讀寫鎖,並且支援公平和非公平獲取鎖兩種模式。 **為什麼會出現讀寫鎖?** 普通鎖可以保證共享資料在同一時刻只被一個執行緒訪問,就算有多個執行緒都只是讀取的操作,也還是要排隊等待獲取鎖,我們知道資料如果只涉及到讀操作,是不會出現執行緒安全方面的問題的,那這部分加鎖是不是可以去掉?或者是加鎖不互斥?如果在讀多寫少的情況下,使用普通的鎖,在所有讀的情況加鎖互斥等待會是一個及其影響系統併發量的問題,如果所有的讀操作不互斥,只有涉及到寫的時候才互斥,這樣會不會大大的提高併發量呢?答案是肯定的,ReentrantReadWriteLock 就是這樣乾的,讀讀不互斥,讀寫、寫讀、寫寫都是互斥的,可以大大提高系統併發量。 ## 原始碼分析 ### 類結構 ReentrantReadWriteLock 僅實現了ReadWriteLock介面 ```java public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializable {...} ``` ReadWriteLock 介面僅有兩個方法,分別是 `readLock()` 和 `writeLock()`; ### 主要屬性 ReentrantReadWriteLock 有3個重要的屬性,分別是讀鎖readerLock,寫鎖writerLock和同步器sync,原始碼如下: ```java private final ReentrantReadWriteLock.ReadLock readerLock; private final ReentrantReadWriteLock.WriteLock writerLock; final Sync sync; ``` ### 主要內部類 1. Sync:同步器,繼承至AbstractQueuedSynchronizer,定義了兩個抽象方法,用於兩種模式下自定義實現判斷是否要阻塞 ```java abstract static class Sync extends AbstractQueuedSynchronizer{ ... abstract boolean readerShouldBlock(); abstract boolean writerShouldBlock(); ... } ``` 2. NonfairSync:非公平同步器,用於實現非公平鎖,繼承Sync ```java static final class NonfairSync extends Sync {...} ``` 3. FairSync:公平同步器,用於實現公平鎖,繼承Sync ```java static final class FairSync extends Sync {...} ``` 4. ReadLock:讀鎖,實現了Lock介面,持有同步器Sync的具體例項 ```java public static class ReadLock implements Lock, java.io.Serializable { ... private final Sync sync; ... } ``` 5. WriteLock:寫鎖,實現了Lock介面,持有同步器Sync的具體例項 ```java public static class WriteLock implements Lock, java.io.Serializable { ... private final Sync sync; ... } ``` ### 構造方法 有兩個預設的構造方法,無參預設採用非公平鎖,有參傳入true使用公平鎖 ```java public ReentrantReadWriteLock() { this(false); } public ReentrantReadWriteLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); readerLock = new ReadLock(this); writerLock = new WriteLock(this); } ``` ### 獲取讀寫鎖 ```java public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; } public ReentrantReadWriteLock.ReadLock readLock() { return readerLock; } ``` ### 獲取讀鎖:readLock.lock() 讀鎖主要是按照共享模式來獲取鎖的,在前面講AQS的例子中——基於AQS實現自己的共享鎖,也是差不多的流程,只不過不同的鎖的實現方法tryAcquireShared有一定的區別。ReentrantReadWriteLock 讀鎖獲取過程原始碼如下: ```java public void lock() { // 共享模式獲取鎖 sync.acquireShared(1); } // acquireShared 是AQS框架裡面的程式碼 public final void acquireShared(int arg) { if (tryAcquireShared(arg) < 0) doAcquireShared(arg); } // tryAcquireShared 是RRWLock.Sync 裡面的自己實現,所以這裡沒有公平和非公平所謂之稱 protected final int tryAcquireShared(int unused) { // 當前想要獲得鎖的執行緒 Thread current = Thread.currentThread(); // 獲取state值 int c = getState(); // 獨佔鎖被佔用了,並且不是當前執行緒佔有的,返回-1,出去要排隊 if (exclusiveCount(c) != 0 && getExclusiveOwnerThread() != current) return -1; // 讀鎖共享鎖的次數 int r = sharedCount(c); // 判斷讀是否要阻塞,讀共享鎖的次數是否超過最大值,CAS 更新鎖state值 // readerShouldBlock 的返回要根據同步器是否公平的具體實現來決定 if (!readerShouldBlock() && r < MAX_COUNT && compareAndSetState(c, c + SHARED_UNIT)) { if (r == 0) { // r==0, 設定第一次獲得讀鎖的讀者 firstReader = current; firstReaderHoldCount = 1; } else if (firstReader == current) { // 持有第一個讀者讀鎖的執行緒重入計數 firstReaderHoldCount++; } else { // 除第一個執行緒之後的其他執行緒獲得讀鎖 // 每個執行緒每次獲得讀鎖重入計數+1 // readHolds 就是一個ThreadLocal,裡面放的HoldCounter,用來統計每個執行緒的重入次數 HoldCounter rh = cachedHoldCounter; if (rh == null || rh.tid != getThreadId(current)) cachedHoldCounter = rh = readHolds.get(); else if (rh.count == 0) readHolds.set(rh); rh.count++; } // 獲得讀鎖,返回1 return 1; } // 上面if分支沒進去時,走這裡嘗試獲取讀鎖 return fullTryAcquireShared(current); } ``` 上面程式碼中的`readerShouldBlock()`方法有兩種情況下會返回true: 1. 公平模式下,呼叫的`AQS.hasQueuedPredecessors()`方法 ```java static final class FairSync extends Sync { final boolean writerShouldBlock() { return hasQueuedPredecessors(); } final boolean readerShouldBlock() { return hasQueuedPredecessors(); } } public final boolean hasQueuedPredecessors() { Node t = tail; // Read fields in reverse initialization order Node h = head; Node s; // head 頭結點是當前持有鎖的節點,它的下一個節點不是當前執行緒,返回true,表示應該要阻塞當前執行緒 return h != t && ((s = h.next) == null || s.thread != Thread.currentThread()); } ```