原始碼分析:ReentrantReadWriteLock之讀寫鎖
阿新 • • 發佈:2020-11-18
## 簡介
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());
}
```