本文將介紹Java中ReentrantReadWriteLock的實現原理,從JDK源碼層面講解讀寫鎖的加鎖、釋放鎖的流程,最後對流程進行總結。
讀寫鎖概述
讀寫鎖 ReentrantReadWriteLock 的依賴關係如下圖所示。
讀寫鎖的基本使用如下
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
ReentrantReadWriteLock.ReadLock readLock = lock.readLock();
readLock.lock();
readLock.unlock();
ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock();
writeLock.lock();
writeLock.unlock();
讀鎖和寫鎖使用同一個 Sync 同步器,即使用同一個等待隊列和 state。讀鎖狀態使用 state 高 16 位存儲,寫鎖狀態使用 state 低 16 位存儲。
讀鎖涉及兩個重入計數:state(高 16 位)用於記錄有多少個線程持有該讀鎖,HoldCounter 用於記錄當前線程重入該讀鎖多少次,每個線程均有一個對應的 HoldCounter 對象。
寫鎖涉及一個重入計數:state(低 16 位)用於記錄有多少個線程持有該寫鎖。
讀鎖不支持條件變量,寫鎖支持條件變量。
寫鎖加鎖流程
WriteLock 的 lock() 方法會調用同步器的 acquire()方法。
// WriteLock implements Lock,
public void lock() {
sync.acquire(1);
}
同步器的 acquire() 方法會先調用 tryAcquire() 方法嘗試獲取寫鎖,獲取寫鎖失敗則調用 AbstractQueuedSynchronizer 的全參 acquire() 方法將線程加入等待隊列。
// AbstractQueuedSynchronizer
public final void acquire(int arg) {
if (!tryAcquire(arg))
// 獲取寫鎖失敗則將線程加入等待隊列
acquire(null, arg, false, false, false, 0L);
}
在介紹 ReentrantLock 的實現原理時,已對 AbstractQueuedSynchronizer 的全參 acquire() 方法進行了較為詳細的介紹,可參照 源碼解讀 | Java中ReentrantLock的實現原理,此處不再贅述,重點來看一下 tryAcquire() 方法如何獲取寫鎖。
// Sync extends AbstractQueuedSynchronizer
@ReservedStackAccess
protected final boolean tryAcquire(int acquires) {
Thread current = Thread.currentThread();
int c = getState();
// 寫鎖狀態
int w = exclusiveCount(c);
// 已加寫鎖或者讀鎖
if (c != 0) {
if (
// 已加讀鎖:讀寫互斥,不能再加寫鎖
w == 0 ||
// 已加寫鎖,但不是自己加的(非寫鎖重入):寫寫互斥,不能再加寫鎖
current != getExclusiveOwnerThread()
)
// 加寫鎖失敗
return false;
// 已加寫鎖且是自己加的
// 整數溢出
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// 寫鎖重入
setState(c + acquires);
return true;
}
// 未加鎖
if (
// 寫鎖應該等待
writerShouldBlock() ||
// 嘗試獲取寫鎖失敗
!compareAndSetState(c, c + acquires)
)
// 加鎖失敗
return false;
// 加鎖成功
// 將鎖的持有者置為當前線程
setExclusiveOwnerThread(current);
return true;
}
NonfairSync 和 FairSync 對 writerShouldBlock() 有不同的實現。對於非公平鎖,當前寫線程可直接參與競爭,不應該等待,競爭失敗才加入等待隊列。對於公平鎖,當前寫線程不應該直接參與競爭,應該等待,加入等待隊列。
// NonfairSync extends Sync
final boolean writerShouldBlock() {
// 直接參與競爭,不等待
return false;
}
// FairSync extends Sync
final boolean writerShouldBlock() {
// 等待隊列中是否存在未取消的等待線程
// 存在未取消的線程則需要等待
return hasQueuedPredecessors();
}
讀鎖加鎖流程
ReadLock 的 lock() 方法會調用同步器的 acquireShared() 方法。
// ReadLock implements Lock
public void lock() {
sync.acquireShared(1);
}
同步器的 acquireShared() 方法會先調用 tryAcquireShared() 方法嘗試獲取讀鎖,獲取讀鎖失敗則調用 AbstractQueuedSynchronizer 全參的 acquire() 方法將線程加入等待隊列。
// AbstractQueuedSynchronizer
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
// 獲取讀鎖失敗則將線程加入等待隊列
acquire(null, arg, true, false, false, 0L);
}
在介紹 ReentrantLock 的實現原理時,已對 AbstractQueuedSynchronizer 的全參 acquire() 方法進行了較為詳細的介紹,可參照 [ReentrantLock 原理](),此處不再贅述,重點來看一下 tryAcquireShared() 方法如何獲取寫鎖。
// Sync extends AbstractQueuedSynchronizer
@ReservedStackAccess
protected final int tryAcquireShared(int unused) {
Thread current = Thread.currentThread();
int c = getState();
if (
// 已加寫鎖
exclusiveCount(c) != 0 &&
// 寫鎖不是自己加的
getExclusiveOwnerThread() != current
)
// 寫讀互斥,獲取鎖失敗
return -1;
// 讀鎖狀態
int r = sharedCount(c);
if (
// 讀鎖不應該等待
!readerShouldBlock() &&
// 未越界
r < MAX_COUNT &&
// 嘗試獲取讀鎖成功
compareAndSetState(c, c + SHARED_UNIT)
) {
// 以下三個if-else分支用於讀鎖重入計數,忽略不影響理解主要加鎖解鎖流程
// 未加讀鎖
if (r == 0) {
// 第一個獲取讀鎖的線程
firstReader = current;
firstReaderHoldCount = 1;
}
// 第一個獲取讀鎖的線程重入
else if (firstReader == current) {
firstReaderHoldCount++;
}
// 已加讀鎖且當前線程非讀重入
else {
// cachedHoldCounter緩存最近(上一個)獲取讀鎖的線程的計數器
// rh:recent HoldCounter
HoldCounter rh = cachedHoldCounter;
if (
// 上一個獲取讀鎖的線程為null
rh == null ||
// 上一個獲取讀鎖的線程不是當前線程
rh.tid != LockSupport.getThreadId(current)
)
// 獲取當前線程的計數器並緩存
cachedHoldCounter = rh = readHolds.get();
else if (
// 上一個獲取讀鎖的線程為當前線程且計數為0(已被移除)
rh.count == 0
)
// 重新將當前線程放入哈希表記錄
readHolds.set(rh);
// 更新計數(計數加1)
rh.count++;
}
return 1;
}
// 讀線程應該等待或者嘗試獲取鎖失敗或者越界
// 再嘗試獲取讀鎖
return fullTryAcquireShared(current);
}
NonfairSync 和 FairSync 對 readerShouldBlock() 有不同的實現。對於非公平鎖,只要等待隊列第一個等待線程不是寫線程,那麼當前讀線程就可直接參與競爭,不應該等待,競爭失敗才加入等待隊列。對於公平鎖,當前讀線程不應該直接參與競爭,應該等待,加入等待隊列。
// NonfairSync extends Sync
final boolean readerShouldBlock() {
// 等待隊列第一個等待線程是否為寫線程
// 若為寫線程,則讀線程需要等待
// 不直接返回false是為避免寫線程無限等待
return apparentlyFirstQueuedIsExclusive();
}
// FairSync extends Sync
final boolean readerShouldBlock() {
// 等待隊列中是否存在未取消的等待線程
// 存在未取消的線程則需要等待
return hasQueuedPredecessors();
}
在 AbstractQueuedSynchronizer 的 全參acquire() 方法中,如果喚醒的是讀線程,會調用 tryAcquireShared() 方法嘗試獲取鎖,獲取成功後,如果下一個等待線程是讀線程,則會喚醒下一個等待線程。
// 如果讀線程被喚醒,則調用 tryAcquireShared() 方法嘗試獲取鎖
if (shared)
acquired = (tryAcquireShared(arg) >= 0);
// 如果讀線程被喚醒並獲取鎖成功,並且下一個等待線程仍為讀線程,則由當前讀線程直接去喚醒下一個等待線程
if (acquired) {
...
if (shared)
signalNextIfShared(node);
...
}
// AbstractQueuedSynchronizer
private static void signalNextIfShared(Node h) {
Node s;
// 如果下一個等待線程為讀線程,則喚醒下一個線程
if (h != null && (s = h.next) != null &&
(s instanceof SharedNode) && s.status != 0) {
s.getAndUnsetStatus(WAITING);
LockSupport.unpark(s.waiter);
}
}
寫鎖釋放鎖流程
WriteLock 的 unlock() 方法會調用同步器的 release()方法
// WriteLock
public void unlock() {
sync.release(1);
}
同步器的 release() 方法會先調用 tryRelease() 方法嘗試釋放寫鎖,釋放成功後將喚醒下一個線程。
// AbstractQueuedSynchronizer
public final boolean release(int arg) {
if (tryRelease(arg)) {
signalNext(head);
return true;
}
return false;
}
公平鎖和非公平鎖釋放寫鎖的流程相同。
// Sync extends AbstractQueuedSynchronizer
@ReservedStackAccess
protected final boolean tryRelease(int releases) {
// 寫鎖的持有者才能釋放寫鎖
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
// 更新重入次數
int nextc = getState() - releases;
boolean free = exclusiveCount(nextc) == 0;
// 重入次數為0則將寫鎖的持有者置為null
if (free)
setExclusiveOwnerThread(null);
setState(nextc);
// 重入次數為0時認為寫鎖釋放成功
return free;
}
讀鎖釋放流程
ReadLock 的 unlock() 方法會調用 同步器的 releaseShared() 方法。
// ReadLock implements Lock
public void unlock() {
sync.releaseShared(1);
}
同步器的 releaseShared() 方法會先調用 tryReleaseShared() 方法嘗試釋放鎖,釋放成功後則會喚醒下一個等待線程。
// AbstractQueuedSynchronizer
public final boolean releaseShared(int arg) {
// 嘗試釋放讀鎖
if (tryReleaseShared(arg)) {
// 喚醒下一等待線程
signalNext(head);
return true;
}
return false;
}
公平鎖和非公平鎖釋放讀鎖的流程相同。注意到,釋放讀鎖時使用循環反覆嘗試,因為讀鎖是共享的,可被多個線程持有。
// Sync extends AbstractQueuedSynchronizer
protected final boolean tryReleaseShared(int unused) {
Thread current = Thread.currentThread();
// ...修改線程重入計數...
// 嘗試釋放鎖
for (;;) {
int c = getState();
int nextc = c - SHARED_UNIT;
if (compareAndSetState(c, nextc))
// 重入次數為0時認為讀鎖釋放成功
return nextc == 0;
}
}
讀寫鎖流程總結
寫鎖加鎖流程
獲取寫鎖失敗的情況
- 已加讀鎖,讀寫互斥;
- 已加寫鎖且非重入,寫寫互斥;
- 公平鎖:等待隊列存在未取消的線程。
獲取寫鎖前必須釋放讀鎖:
若線程 t 加讀鎖後,在釋放讀鎖前,又加寫鎖,由於已加讀鎖,寫鎖獲取失敗,線程 t 陷入等待,又導致讀鎖無法釋放,從而產生死鎖。
讀鎖加鎖流程
獲取讀鎖失敗的情況
- 已加寫鎖非重入,寫讀互斥;
- 非公平鎖:等待隊列中第一個等待線程為寫線程;
- 公平鎖:等待隊列存在未取消的線程。
讀線程被喚醒後,如果等待隊列下一個等待線程為讀線程,則會直接將其喚醒。
寫鎖釋放流程
嘗試釋放寫鎖,釋放成功後將下一個等待線程喚醒。
如果線程不是寫鎖的持有者而去嘗試釋放寫鎖,則會拋異常 IllegalMonitorStateException。
// Sync extends AbstractQueuedSynchronizer
// tryRelease()
//
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
僅當寫鎖重入計數為 0 時,才認為寫鎖釋放成功。
讀鎖釋放流程
嘗試釋放讀鎖,釋放成功後將下一個等待線程喚醒。
如果線程不是讀鎖的持有者而去嘗試釋放讀鎖,則會拋異常 unmatchedUnlockException。
// Sync extends AbstractQueuedSynchronizer
// tryReleaseShared()
if (count <= 0)
// 重入計數小於等於0説明當前線程未持有讀鎖
throw unmatchedUnlockException();
僅當讀鎖重入計數為 0 時,才認為讀鎖釋放成功。
END
如果覺得本文對您有一點點幫助,歡迎點贊、轉發加關注,這會對我有非常大的幫助,如果有任何問題,歡迎在評論區留言或者後台私信,咱們下期見!
文章文檔:公眾號 字節幺零二四 回覆關鍵字可獲取本文文檔。