ReentrantReadWriteLock 原始碼分析以及 AQS 共享鎖 (二)
阿新 • • 發佈:2020-03-17
## 前言
上一篇講解了 AQS 的獨佔鎖部分(參看:[ReentrantLock 原始碼分析以及 AQS (一)](https://mp.weixin.qq.com/s/dDjbR76U5C696CXAOAvpng)),這一篇將介紹 AQS 的共享鎖,以及基於共享鎖實現讀寫鎖分離的 ReentrantReadWriteLock。(若是遇到之前講過的方法,將不再贅述)
**先思考一下,為什麼我們用讀寫鎖分離?**
我們知道 ReentrantLock 用的是獨佔鎖,不管執行緒是讀還是寫狀態,都會阻塞,這無疑會降低併發量。
但是,我們知道多個執行緒同時去讀資料的時候,並不會產生執行緒安全的問題,因為它們互不干擾。那麼為什麼不設計一種方案,讓所有的讀執行緒可以共享,一起同時讀資料呢,只需要阻塞寫的執行緒就可以了。提高併發的同時,也不會產生資料不一致的現象。
同樣的,如果有執行緒在寫資料,那麼也會阻塞其它讀執行緒(同樣阻塞其它寫執行緒),資料寫完之後才可以讀資料,這樣保證讀到的資料都是最新的。
因此,我們可以用讀、寫兩把鎖,分別控制資料的讀和寫。實現讀讀共享、讀寫互斥,寫寫互斥。這也是 ReentrantReadWriteLock 讀寫分離鎖的由來。它非常適合用在讀多寫少的場景。
## ReentrantReadWriteLock
它和 ReentrantLock 一樣,也是一個可重入的鎖,並基於 AQS 共享鎖實現了讀寫分離。其內部結構也大同小異,支援公平鎖和非公平鎖。我們看下它的建構函式,
```
public ReentrantReadWriteLock() {
//預設非公平
this(false);
}
public ReentrantReadWriteLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
readerLock = new ReadLock(this);
writerLock = new WriteLock(this);
}
```
它定義了兩個內部類來表示讀鎖和寫鎖,並且都通過內部類 Sync 來實現加鎖,釋放鎖等功能。
```
public static class ReadLock implements Lock, java.io.Serializable {
private static final long serialVersionUID = -5992448646407690164L;
private final Sync sync;
protected ReadLock(ReentrantReadWriteLock lock) {
sync = lock.sync;
}
...
}
public static class WriteLock implements Lock, java.io.Serializable {
private static final long serialVersionUID = -4992448646407690164L;
private final Sync sync;
protected WriteLock(ReentrantReadWriteLock lock) {
sync = lock.sync;
}
...
}
abstract static class Sync extends AbstractQueuedSynchronizer {
}
```
我們再看下公平鎖和非公平鎖,其中有兩個比較重要的方法,用來判斷讀鎖和寫鎖是否應該被阻塞,後面加鎖的時候會用到(其實,實際情況是否真的應該阻塞,還需要斟酌,後面會說)。
```
static final class FairSync extends Sync {
private static final long serialVersionUID = -2274990926593161451L;
//公平鎖的讀和寫都需要判斷,在它前面是否已經有執行緒在等待。
//有的話,當前執行緒就需要阻塞,這也體現了公平性。
final boolean writerShouldBlock() {
return hasQueuedPredecessors();
}
final boolean readerShouldBlock() {
return hasQueuedPredecessors();
}
}
static final class NonfairSync extends Sync {
private static final long serialVersionUID = -8159625535654395037L;
//非公平鎖,寫的時候不需要阻塞,直接返回false
final boolean writerShouldBlock() {
return false; // writers can always barge
}
final boolean readerShouldBlock() {
//為了避免寫執行緒飢餓,需要判斷同步佇列中第一個排隊的(head.next)是否是獨佔鎖(寫執行緒)
//如果是的話,當前讀執行緒就需要阻塞,這是 AQS 中的方法
return apparentlyFirstQueuedIsExclusive();
}
}
final boolean apparentlyFirstQueuedIsExclusive() {
Node h, s;
return (h = head) != null &&
(s = h.next) != null &&
!s.isShared() &&
s.thread != null;
}
```
**思考:**
我們知道 ReentrantLock 的同步狀態和重入次數,是直接用 state 值來表示的。那麼,現在我需要讀和寫兩把鎖,怎麼才能用一個 int 型別的值來表示兩把鎖的狀態呢?並且,鎖是可重入的,重入的次數怎麼記錄呢?
別急,下面一個一個說。
### 怎麼用一個 state 值表示讀、寫兩把鎖?
![](https://img2020.cnblogs.com/other/1714084/202003/1714084-20200317204510064-1840955138.jpg)
state 是一個 32 位的 int 值,讀寫鎖中,把它一分為二,高 16 位用來表示讀狀態,其值代表讀鎖的執行緒數,如圖中為 3 個,低 16位表示寫狀態,其值代表寫鎖的重入次數(因為是獨佔鎖)。 這樣,就可以分別計算讀鎖和寫鎖的個數了。其相關的屬性和方法定義在 Sync 類中。
```
static final int SHARED_SHIFT = 16;
//表明讀鎖每增加一個,state的實際值增加 2^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;
//持有讀鎖的執行緒個數,引數如的 c 代表 state值
//state 的32位二進位制位,無符號右移 16位之後,其實就是高16位的值
static int sharedCount(int c) { return c >>> SHARED_SHIFT; }
//寫鎖數量,即寫鎖的重入次數
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
```
讀鎖的個數計算比較簡單,直接無符號右移 16 位即可。我們看下寫鎖的重入次數是怎麼計算的。先看下 EXCLUSIVE_MASK 這個值,是 (1 << 16) - 1,我們用二進位制表示計算過程為:
```
// 1的二進位制
0000 0000 0000 0000 0000 0000 0000 0001
// 1左移 16位
0000 0000 0000 0001 0000 0000 0000 0000
//再減 1
0000 0000 0000 0000 1111 1111 1111 1111
//任何一個 32位二進位制數 c,和以上值做 “與” 運算都為它本身 c 的低 16 位值
//這個不用解釋了吧,這個不會的話,需要好好補充一下基礎知識了。。。
```
### 鎖的重入次數是怎麼計算的?
寫鎖比較簡單,直接用計算出來的低16位值就可以代表寫鎖的重入次數。
讀鎖,就比較複雜了,因為高16位只能表示持有共享鎖的執行緒個數,實在是分身乏術啊。所以,在 Sync 內部,維護了一個類,用來表示每個執行緒重入的次數,
```
static final class HoldCounter {
int count = 0;
// Use id, not reference, to avoid garbage retention
final long tid = getThreadId(Thread.currentThread());
}
```
這裡邊定義了一個計數器來表示重入次數,tid 來表示當前的執行緒 id 。但是,這樣還不夠,我們需要把 HoldCounter 和 執行緒繫結,這樣才可以區分出來每個執行緒分別持有的鎖個數(重入次數),這就需要用到 ThreadLocal 了。
```
static final class ThreadLocalHoldCounter
extends Thr