簡介
ReentrantReadWriteLock即可重入讀寫鎖,同樣也依賴於AQS來實現。在介紹ReentrantLock我們知道其依託AQS的同步狀態來判斷鎖是否佔有,而ReentrantReadWriteLock既有讀鎖又有寫鎖,是如何依靠一個狀態來維持的?
ReentrantReadWriteLock
ReentrantReadWriteLock讀寫鎖,與ReentrantLock一樣預設非公平,內部定義了讀鎖ReadLock()和寫鎖WriteLock(),在同一時間允許被多個讀執行緒訪問,但在寫執行緒訪問時,所有讀執行緒和寫執行緒都會被阻塞。讀寫鎖主要特性:公平性、可重入性、鎖降級
寫鎖的獲取與釋放
寫鎖是一個支援重進入的排它鎖,其獲取的核心方法:
protected final boolean tryAcquire(int acquires) {
// 獲取當前執行緒
Thread current = Thread.currentThread();
// 獲取ReentrantReadWriteLock鎖整體同步狀態
int c = getState();
// 獲取寫鎖同步狀態
int w = exclusiveCount(c);
// 存在讀鎖或寫鎖
if (c != 0) {
// c != 0 && w == 0 即若存在讀鎖或寫鎖持有執行緒不是當前執行緒,獲取寫鎖失敗
if (w == 0 || current != getExclusiveOwnerThread())
return false;
// 最多65535次重入,若超過報錯
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// 可重入,設定同步狀態
setState(c + acquires);
return true;
}
// 公平與非公平,同步佇列是否有節點,同時cas設定狀態
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))
return false;
// 設定獲取鎖的執行緒為當前執行緒
setExclusiveOwnerThread(current);
return true;
}
複製程式碼
從原始碼中我們可以發現getState()獲取的是讀鎖與寫鎖總同步狀態,再通過exclusiveCount()方法單獨獲取寫鎖同步狀態
static final int SHARED_SHIFT = 16;
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
static int exclusiveCount(int c) {
return c & EXCLUSIVE_MASK;
}
複製程式碼
ReentrantReadWriteLock通過按位切割state變數,同步狀態的低16位表示寫鎖獲取次數,高16位表示讀鎖獲取次數,如圖示意
所以這解釋了為什麼寫鎖獲取次數最多65535次
寫鎖獲取整體思路:當讀鎖已經被讀執行緒獲取或者寫鎖已經被其他寫執行緒獲取,則寫鎖獲取失敗;否則,獲取成功並可重入,增加寫鎖同步狀態
protected final boolean tryRelease(int releases) {
// 若釋放的執行緒不為鎖的持有者
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
// 重新設定同步狀態
int nextc = getState() - releases;
// 若新的寫鎖持有執行緒數為0,則將鎖的持有執行緒置為null
boolean free = exclusiveCount(nextc) == 0;
if (free)
setExclusiveOwnerThread(null);
// 更新同步狀態
setState(nextc);
return free;
}
複製程式碼
寫鎖的釋放與ReentrantLock的釋放過程基本類似,每次釋放均減少寫狀態,當寫狀態為0
時表示寫鎖已被釋放,從而等待的讀寫執行緒能夠繼續訪問讀寫鎖,同時前次寫執行緒的修改對後續讀寫執行緒可見
讀鎖的獲取與釋放
讀鎖相對於寫鎖(獨佔鎖或排他鎖),讀鎖是一個支援重進入的共享鎖,它能夠被多個執行緒同時獲取,在沒有其他寫執行緒訪問(或者寫狀態為0)時,讀鎖總會被成功地獲取,而所做的也只是(執行緒安全的)增加讀狀態
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);
// 判斷讀鎖是否需要公平,讀鎖持有執行緒數是否小於極值,CAS設定讀鎖狀態
if (!readerShouldBlock() &&
r < MAX_COUNT &&
compareAndSetState(c, c + SHARED_UNIT)) {
// 若讀鎖未被執行緒佔有,則更新firstReader和firstReaderHoldCount
if (r == 0) {
firstReader = current;
firstReaderHoldCount = 1;
// 如果獲取讀鎖的執行緒為第一次獲取讀鎖的執行緒,則firstReaderHoldCount重入數 + 1
} else if (firstReader == current) {
firstReaderHoldCount++;
} else {
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
cachedHoldCounter = rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
}
return 1;
}
return fullTryAcquireShared(current);
}
複製程式碼
讀鎖獲取整體思路:
①.判斷寫鎖是否被佔有,寫鎖佔有執行緒是否不是當前執行緒,若成立則讀鎖獲取失敗
②.判斷讀鎖是否需要公平,讀鎖持有執行緒數是否小於極值,CAS設定讀鎖狀態成功,若條件不滿足,會呼叫fullTryAcquireShared()方法自旋再次嘗試獲取讀鎖;若條件滿足修改當前執行緒HoldCounter的值
final int fullTryAcquireShared(Thread current) {
HoldCounter rh = null;
for (;;) {
int c = getState();
// 若寫鎖已被佔有,且寫鎖佔有執行緒不是當前執行緒
if (exclusiveCount(c) != 0) {
if (getExclusiveOwnerThread() != current)
return -1;
// 公平性
} else if (readerShouldBlock()) {
// Make sure we're not acquiring read lock reentrantly
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");
// cas設定成功
if (compareAndSetState(c, c + SHARED_UNIT)) {
// 若讀鎖未被執行緒佔有,則更新firstReader和firstReaderHoldCount
if (sharedCount(c) == 0) {
firstReader = current;
firstReaderHoldCount = 1;
// 如果獲取讀鎖的執行緒為第一次獲取讀鎖的執行緒,則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;
}
}
}
複製程式碼
protected final boolean tryReleaseShared(int unused) {
Thread current = Thread.currentThread();
// 若當前執行緒為第一個獲取讀鎖的執行緒
if (firstReader == current) {
// 若只有獲取一次,將firstReader置為null
if (firstReaderHoldCount == 1)
firstReader = null;
// 若多次,firstReaderHoldCount-1
else
firstReaderHoldCount--;
} 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;
}
// 自旋CAS更新讀鎖同步狀態
for (;;) {
int c = getState();
int nextc = c - SHARED_UNIT;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
複製程式碼
static final class HoldCounter { int count = 0; final long tid = getThreadId(Thread.currentThread()); }
複製程式碼static final class ThreadLocalHoldCounter extends ThreadLocal<HoldCounter> { public HoldCounter initialValue() { return new HoldCounter(); } } private transient HoldCounter cachedHoldCounter; private transient int firstReaderHoldCount; private transient Thread firstReader = null; 複製程式碼
複製程式碼
鎖降級
鎖降級指的是寫鎖降級成為讀鎖,即先獲取寫鎖、獲取讀鎖在釋放寫鎖的過程,目的為了保證資料的可見性。假設有兩個執行緒A、B,若執行緒A獲取到寫鎖,不獲取讀鎖而是直接釋放寫鎖,這時執行緒B獲取了寫鎖並修改了資料,那麼執行緒A無法知道執行緒B的資料更新。如果執行緒A獲取讀鎖,即遵循鎖降級的步驟,則執行緒B將會被阻塞,直到執行緒A使用資料並釋放讀鎖之後,執行緒B才能獲取寫鎖進行資料更新。
總結
當有執行緒獲取讀鎖時,不允許再有執行緒獲得寫鎖
當有執行緒獲得寫鎖時,不允許其他執行緒獲得讀鎖和寫鎖
寫鎖能降級為讀鎖,讀鎖無法升級成寫鎖
感謝
《Java併發程式設計的藝術》
http://cmsblogs.com/?p=2213