上篇主要講的ReentrantReadWriteLock
的幾個核心變量:源碼解讀之(五)ReentrantReadWriteLock(上)
這篇中篇主要來講解講解ReentrantReadWriteLock
的ReadLock
的加鎖和減鎖過程。
三、ReadLock
-
ReentrantReadWriteLock
的ReadLock
加鎖解鎖過程依賴於AQS類,所以有些相同的邏輯可以看看ReentrantLock的邏輯。 -
ReentrantReadWriteLock
的ReadLock
的喚醒過程具備傳播性:假設按照順序A->B->C->D佔用讀鎖,喚醒會依次進行
A線程佔用讀鎖被喚醒後,A線程的鎖釋放會喚醒B線程。
B線程佔用讀鎖被喚醒後,B線程的鎖釋放會喚醒C線程。
C線程佔用讀鎖被喚醒後,C線程的鎖釋放會喚醒D線程。
1、加鎖過程
- ReadLock的lock()內部通過sync. acquireShared(1)獲取鎖。
- acquireShared()方法內部先通過tryAcquireShared嘗試獲取鎖。
- 如果獲鎖失敗執行doAcquireShared()方法加入等待隊列。
public void lock() {
sync.acquireShared(1);
}
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
(一)、tryAcquireShared過程
- 如果當前鎖寫狀態不爲0且佔鎖線程非當前線程,那麼返回佔鎖失敗的值-1。
- 如果公平策略沒有要求阻塞且重入數沒有到達最大值,則直接嘗試cas更新state。
- 如果cas操作成功,有以下操作邏輯:
- 首先,如果當前讀鎖計數爲0那麼就設置第一個讀線程就是當前線程。
- 其次,當前線程和firstReader同一個線程,記錄firstReaderHoldCount也就是第一個讀線程讀鎖定次數。
- 最後,讀鎖數量不爲0並且不爲當前線程,獲取當前線程ThreadLocal當中的讀鎖重入計數器。
- 結果返回佔鎖成功的值1
- 如果cas操作失敗,有以下操作邏輯:
- 通過fullTryAcquireShared嘗試獲取讀鎖,內部處理和tryAcquireShared過程相同。
protected final int tryAcquireShared(int unused) {
Thread current = Thread.currentThread();
int c = getState();
// 如果當前鎖寫狀態不爲0且佔鎖線程非當前線程,那麼返回佔鎖失敗。
// 也就是當前線程先佔寫鎖後可以再佔讀鎖的,反之不行。
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
// 判斷高位的讀狀態標記
int r = sharedCount(c);
//如果公平策略沒有要求阻塞且重入數沒有到達最大值,則直接嘗試CAS更新state
//如果讀不應該阻塞並且讀鎖的個數小於最大值65535,並且可以成功更新狀態值
if (!readerShouldBlock() &&
r < MAX_COUNT &&
compareAndSetState(c, c + SHARED_UNIT)) {
// 如果當前線程滿足對state的操作條件,
// 就利用CAS設置state+SHARED_UNIT,實際上就是讀狀態+1。
// 但是需要注意,這個state是全局的,即所有線程獲取讀鎖次數的總和,
// 而爲了方便計算本線程的讀鎖次數以及釋放掉鎖,
// 需要在ThreadLocal中維護一個變量。這就是HoldCounter。
//如果當前讀鎖爲0
if (r == 0) {
// 第一個讀線程就是當前線程
firstReader = current;
firstReaderHoldCount = 1;
}
//如果當前線程重入了,記錄firstReaderHoldCount
else if (firstReader == current) {
firstReaderHoldCount++;
}
//當前讀線程和第一個讀線程不同,記錄每一個線程讀的次數
else {
// 每個線程自己維護cachedHoldCounter
HoldCounter rh = cachedHoldCounter;
// 計數器爲空或者計數器的tid不爲當前正在運行的線程的tid
if (rh == null || rh.tid != getThreadId(current))
cachedHoldCounter = rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
}
return 1;
}
//用來處理CAS沒成功的情況,邏輯和上面的邏輯是類似的,就是加了無限循環
return fullTryAcquireShared(current);
}
(二)、fullTryAcquireShared過程
- fullTryAcquireShared內部通過for()循環進行邏輯操作。
- 內部處理和tryAcquireShared過程相同。
final int fullTryAcquireShared(Thread current) {
HoldCounter rh = null;
// 無限循環
for (;;) {
// 獲取狀態
int c = getState();
// 寫線程數量不爲0且當前線程不是寫線程那麼返回獲鎖失敗
if (exclusiveCount(c) != 0) {
if (getExclusiveOwnerThread() != current)
return -1;
}
// 寫線程數量爲0並且讀線程被阻塞
else if (readerShouldBlock()) {
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;
}
}
}
(三)、doAcquireShared過程
- doAcquireShared主要實現獲讀鎖失敗後的等待操作。
- doAcquireShared通過addWaiter(Node.SHARED)將當前線程封裝成SHARED類型Node並添加到CLH隊列。
- 如果當前線程的Node節點是CLH隊列的第一個節點則當前線程直接獲取鎖並開啓讀鎖的擴散喚醒所有阻塞讀鎖的線程。
- 如果當前線程的Node節點不是CLH隊列的第一個節點那麼就通過parkAndCheckInterrupt進入休眠。
- doAcquireShared的內部的自旋保證了線程被喚醒後再次判斷是否是第一個節點並嘗試獲取鎖,失敗再次進入休眠。
private void doAcquireShared(int arg) {
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
// 開始自旋重試
for (;;) {
// 獲取當前線程代表節點的前一個節點
final Node p = node.predecessor();
// 如果前一個節點是head節點(head節點不保存任何線程),
// 表明當前節點是第一個等待喚醒節點
if (p == head) {
// 嘗試獲取鎖
int r = tryAcquireShared(arg);
// 如果獲鎖成功
if (r >= 0) {
// 說明當前線程獲取讀鎖成功,那麼設置當前線程Node爲head
// 同時擴散喚醒相關讀線程,因爲讀線程之間相互不阻塞,可以一起喚醒繼續工作
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
// 如果“當前線程”不是CLH隊列的表頭,
// 則通過shouldParkAfterFailedAcquire()判斷是否需要等待,
// 需要的話,則通過parkAndCheckInterrupt()進行阻塞等待。
// 若阻塞等待過程中,線程被中斷過,則設置interrupted爲true。
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
private Node addWaiter(Node mode) {
// 爲當前線程和給定模式創建和排隊節點
Node node = new Node(Thread.currentThread(), mode);
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
(四)、setHeadAndPropagate過程
- 設置當前線程的Node爲CLH隊列的head節點。
- 判斷當前節點的後置節點爲空或者是SHARED狀態那麼就喚起後置的讀鎖阻塞線程。
- doReleaseShared在解鎖過程也同樣提及,放到後面解釋。
private void setHeadAndPropagate(Node node, int propagate) {
// 設置當前節點爲新的head節點
Node h = head;
setHead(node);
// 如果還有剩餘量,繼續喚醒下一個鄰居線程
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
// 當前節點的後置節點
Node s = node.next;
if (s == null || s.isShared())
// 喚醒後起等待線程
doReleaseShared();
}
}
private void doReleaseShared() {
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h);
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}
2、解鎖過程
- ReadLock的unlock()方法調用sync.releaseShared(1)方法進行釋放。
- 調用tryReleaseShared()方法嘗試釋放鎖,如果釋放成功,調用doReleaseShared嘗試喚醒下一個節點。
public void unlock() {
sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
(一)、tryReleaseShared過程
- 如果是第一個讀線程則修改firstReaderHoldCount,如果不是則修改局部HoldCounter。
- for循環的死循環當中釋放一把鎖並設置全局鎖狀態state。
protected final boolean tryReleaseShared(int unused) {
// 得到調用unlock的線程
Thread current = Thread.currentThread();
//如果是第一個獲得讀鎖的線程,進行解鎖
if (firstReader == current) {
// assert firstReaderHoldCount > 0;
if (firstReaderHoldCount == 1)
firstReader = null;
else
firstReaderHoldCount--;
}
// 否則,線程ThreadLocal的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))
return nextc == 0;
}
}
(二)、doReleaseShared過程
- 如果當前節點狀態爲SIGNAL,那麼就通過unparkSuccessor()方法喚醒後置等待線程
- 成功後則跳出for循環
private void doReleaseShared() {
for (;;) {
Node h = head;
if (h != null && h != tail) {
// 當前節點爲SIGNAL那麼就需要通過unparkSuccessor(p)喚醒後置等待線程
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h);
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
// 跳出循環
if (h == head) // loop if head changed
break;
}
}
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);
}
下一篇 下篇主要來講解講解ReentrantReadWriteLock的WriteLock的加鎖和減鎖過程:
源碼解讀之(五)ReentrantReadWriteLock(下)