前言
前面我們分析了Synchronized(同步鎖),ReentrantLock(獨佔鎖),本篇開始分析ReentrantReadWriteLock(讀是共享鎖,寫是獨佔鎖)。
1、ReentrantReadWriteLock結構圖
2、調用的方法關係圖
3、獲取共享鎖
- ReadLock中的lock方法,源碼如下:
public void lock() {
//Sync繼承AQS,此方法實現在AQS中
sync.acquireShared(1);
}
- AQS中的acquireShared方法,源碼如下
public final void acquireShared(int arg) {
//先嚐試獲取鎖,獲取成功則返回,失敗則執行doAcquireShared方法再去獲取鎖
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
- tryAcquireShared()定義在ReentrantReadWriteLock.java的Sync中,源碼如下:
protected final int tryAcquireShared(int unused) {
Thread current = Thread.currentThread();
// 在讀寫鎖模式下,高16位存儲的是共享鎖(讀鎖)被獲取的次數,低16位存儲的是互斥鎖(寫鎖)被獲取的次數
int c = getState();
//如果獨佔鎖(寫鎖)已經被獲取並且獲取獨佔鎖的線程不是當前線程的話,則返回-1
//如果是獨佔鎖是當前線程獲取,則當前線程也可以獲取讀鎖,鎖降級
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
//獲取“讀取鎖”的共享計數
int r = sharedCount(c);
// 如果不需要阻塞等待,並且“讀取鎖”的共享計數小於MAX_COUNT;
// 則通過CAS函數更新“讀取鎖”的共享計數+1
if (!readerShouldBlock() &&
r < MAX_COUNT &&
compareAndSetState(c, c + SHARED_UNIT)) {
//第一次獲取讀取鎖
if (r == 0) {
firstReader = current;
firstReaderHoldCount = 1;
//如果當前線程是第1個獲取鎖的線程
} else if (firstReader == current) {
firstReaderHoldCount++;
} else {
// HoldCounter統計的是當前線程獲取“讀取鎖”的次數
//下面這幾行,就是將 cachedHoldCounter 設置爲當前線程
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
// cachedHoldCounter 是否緩存的是當前線程,不是的話要到 ThreadLocal 中取
cachedHoldCounter = rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
}
return 1;
}
//if條件失敗,則進入這個方法
return fullTryAcquireShared(current);
}
- fullTryAcquireShared()在ReentrantReadWriteLock.java的Sync中,源碼如下:
final int fullTryAcquireShared(Thread current) {
HoldCounter rh = null;
for (;;) {
int c = getState();
// 如果“鎖”被“寫鎖”持有,並且獲取鎖的線程不是current線程;則返回-1。
if (exclusiveCount(c) != 0) {
if (getExclusiveOwnerThread() != current)
return -1;
// 需要阻塞等待
} else if (readerShouldBlock()) {
//進入這裏,說明寫鎖被釋放,讀鎖被阻塞
//那麼邏輯就很清楚了,這裏是處理讀鎖重入的
if (firstReader == current) {
} else {
if (rh == null) {
rh = cachedHoldCounter;
if (rh == null || rh.tid != current.getId()) {
rh = readHolds.get();
if (rh.count == 0)
readHolds.remove();
}
}
// 如果當前線程獲取鎖的計數=0,則返回-1,去排隊。
if (rh.count == 0)
return -1;
}
}
if (sharedCount(c) == MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// 將線程獲取“讀取鎖”的次數+1。
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 != current.getId())
rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
// 更新線程的獲取“讀取鎖”的共享計數
rh.count++;
cachedHoldCounter = rh; // cache for release
}
return 1;
}
}
}
- doAcquireShared()定義在AQS函數中,源碼如下:
private void doAcquireShared(int arg) {
// addWaiter(Node.SHARED)方法前面分析過,作用是:
//創建“當前線程”對應的節點,並將該線程添加到CLH隊列中
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
// 獲取當前節點上一個節點
final Node p = node.predecessor();
// 如果p是頭節點,則嘗試獲取共享鎖。
if (p == head) {
//此處是上面分析過的tryAcquireShared方法,這裏可以看出:
//doAcquireShared方法其實就是在循環的調用tryAcquireShared來嘗試獲取共享鎖
int r = tryAcquireShared(arg);
//如果成功
if (r >= 0) {
// 頭節點後移並傳播
// 傳播即喚醒後面連續的讀節點
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
//此處判斷是否進行阻塞等待,若阻塞等待過程中,線程被中斷過,則設置interrupted爲true。
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
4、釋放共享鎖
- ReadLock中的unLock方法,源碼如下:
public void unlock() {
//Sync繼承AQS,此方法實現在AQS中
sync.releaseShared(1);
}
- releaseShared()在AQS中實現,源碼如下:
public final boolean releaseShared(int arg) {
//這裏的思路和獲取鎖一樣:
//通過tryReleaseShared()去嘗試釋放共享鎖,成功直接返回
//失敗,則通過doReleaseShared釋放共享鎖
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
- tryReleaseShared()定義在ReentrantReadWriteLock.java的Sync中,源碼如下:
protected final boolean tryReleaseShared(int unused) {
Thread current = Thread.currentThread();
// 如果當前線程是第1個獲取鎖的線程
if (firstReader == current) {
// assert firstReaderHoldCount > 0;
//如果等於 1,那麼這次釋放鎖後就不再持有鎖了,把 firstReader 置爲 null,給後來的線程用
if (firstReaderHoldCount == 1)
firstReader = null;
else
firstReaderHoldCount--;
} else {
// 判斷 cachedHoldCounter 是否緩存的是當前線程,不是的話要到 ThreadLocal 中取
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != current.getId())
rh = readHolds.get();
int count = rh.count;
if (count <= 1) {
// 這一步將 ThreadLocal remove 掉,防止內存泄漏。因爲已經不再持有讀鎖了
readHolds.remove();
if (count <= 0)
//這裏的情況是,lock() 一次,unlock() 好幾次纔會觸發
throw unmatchedUnlockException();
}
//當前線程獲取“讀取鎖”的次數-1
--rh.count;
}
for (;;) {
// 獲取鎖的狀態
int c = getState();
// 將鎖的獲取次數-1。
int nextc = c - SHARED_UNIT;
// 通過CAS更新鎖的狀態。
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
- doReleaseShared()定義在AQS中,源碼如下:
private void doReleaseShared() {
for (;;) {
// 獲取CLH隊列的頭節點
Node h = head;
// 如果頭節點不爲null,並且頭節點不是隊列最後一個節點
if (h != null && h != tail) {
// 獲取頭節點對應的線程的狀態
int ws = h.waitStatus;
// 判斷頭節點是否是SIGNAL狀態
//如果是,下一個節點所對應的線程需要被喚醒。
if (ws == Node.SIGNAL) {
// 設置“頭節點對應的線程狀態”爲0。失敗的話,則繼續循環。
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue;
// 喚醒“頭節點的下一個節點所對應的線程”。
unparkSuccessor(h);
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
// 如果頭節點發生變化,則繼續循環。否則,退出循環。
if (h == head) // loop if head changed
break;
}
}
5、實例
class CachedData {
Object data;
volatile boolean cacheValid;
// 讀寫鎖實例
final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
void processCachedData() {
// 獲取讀鎖
rwl.readLock().lock();
if (!cacheValid) { // 如果緩存過期了,或者爲 null
// 釋放掉讀鎖,然後獲取寫鎖 (後面會看到,沒釋放掉讀鎖就獲取寫鎖,會發生死鎖情況)
rwl.readLock().unlock();
rwl.writeLock().lock();
try {
if (!cacheValid) { // 重新判斷,因爲在等待寫鎖的過程中,可能前面有其他寫線程執行過了
data = ...
cacheValid = true;
}
// 獲取讀鎖 (持有寫鎖的情況下,是允許獲取讀鎖的,稱爲 “鎖降級”,反之不行。)
rwl.readLock().lock();
} finally {
// 釋放寫鎖,此時還剩一個讀鎖
rwl.writeLock().unlock(); // Unlock write, still hold read
}
}
try {
use(data);
} finally {
// 釋放讀鎖
rwl.readLock().unlock();
}
}
}
結束語
本篇到此ReentrantReadWriteLock的共享鎖獲取和釋放的源碼就分析完了,下一篇將對信號量Semaphore進行分析。
如果本篇對你有幫助,請點個贊再走,謝謝大家!