前言
讀寫鎖ReentrantReadWriteLock管理了一組鎖,一個讀鎖,一個寫鎖。當寫鎖被獲取到時,後續(非當前寫操作線程)的讀寫操作都會被阻塞,寫鎖釋放後,所有的操作繼續執行。而當讀鎖被獲取時,後續的任意線程的讀鎖都可被獲取,寫鎖會被阻塞,當讀鎖被完全釋放後,線程可以獲取到寫鎖。可以看出讀鎖是共享式鎖,寫鎖是獨佔式鎖。那在ReentrantReadWriteLock中如何管理讀寫鎖?讀鎖與寫鎖的獲取釋放過程是什麼?
正文
ReentrantReadWriteLock中讀寫鎖分別對應着ReadLock,WriteLock。ReadLock與WriteLock都有一個成員變量Sync。Sync是ReentrantReadWriteLock自定義的同步組件,繼承了同步器AbstractQueuedSynchronizer。讀寫鎖依賴自定義同步組件來實現同步功能,而讀寫狀態就是其同步組件的同步狀態。由於同步器AbstractQueuedSynchronizer的同步狀態是整型變量state,如果利用一個整型變量維護多種狀態,那就需要將該變量按位切割使用。讀寫鎖將該變量分爲兩個部分,高16位表示讀,低16位表示寫。
在ReentrantReadWriteLock自定義的同步組件中有兩個方法sharedCount與exclusiveCount,當傳入的參數是當前同步狀態時分別獲取讀鎖的獲取數量與寫鎖的獲取數量。他們均採用位運算來實現。
static final int SHARED_SHIFT = 16;
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
static int sharedCount(int c) { return c >>> SHARED_SHIFT; }
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
sharedCount方法獲取高位的值,即讀鎖被獲取的數量。exclusiveCount方法獲取低位的值,即寫鎖被獲取的數量。當我們知道在ReentrantReadWriteLock中同步狀態高低位代表不同含義,接下來看一下寫鎖的獲取和釋放。
寫鎖的獲取和釋放
寫鎖的獲取
ReentrantReadWriteLock通過調用WriteLock的lock方法完成寫鎖的獲取,lock方法調用同步器的acquire方法。
public void lock() {
sync.acquire(1);
}
同步器的acquire方法會調用自定義同步組件的tryAcquire方法獲取同步狀態。
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;
}
// CAS設置同步狀態
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))
return false;
// 設置持有鎖的線程
setExclusiveOwnerThread(current);
return true;
}
代碼邏輯清晰明瞭,唯一需要注意的地方是writerShouldBlock方法,該方法判斷該線程是否有資格獲取鎖,這與ReentrantReadWriteLock的公平與否有關。對於ReentrantReadWriteLock默認非公平性實現而言,任何線程都有資格獲取寫鎖。但是如果ReentrantReadWriteLock被設置爲鎖公平競爭,writerShouldBlock需要判斷當前線程是否爲最優獲取鎖的線程。
公平性鎖競爭
final boolean writerShouldBlock() {
return hasQueuedPredecessors();
}
public final boolean hasQueuedPredecessors() {
// 同步隊列尾節點
Node t = tail;
// 同步隊列頭節點
Node h = head;
Node s;
// 判斷頭節點的後繼節點的線程是否爲當前線程,即判斷當前節點是否有前驅節點
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
寫鎖的釋放
ReentrantReadWriteLock通過調用WriteLock的unlock方法完成寫鎖的釋放。
public void unlock() {
sync.release(1);
}
unlock方法調用自定義同步組件的tryRelease方法釋放同步狀態。
protected final boolean tryRelease(int releases) {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
// 重新計算獲取鎖的數量
int nextc = getState() - releases;
// 標記鎖是否被完全釋放
boolean free = exclusiveCount(nextc) == 0;
if (free)
// 鎖完全被釋放,將獲取鎖的線程置空
setExclusiveOwnerThread(null);
// 更新同步狀態
setState(nextc);
return free;
}
讀鎖的獲取和釋放
讀鎖的獲取
ReentrantReadWriteLock會調用自定義同步組件的tryAcquireShared方法獲取同步狀態。
protected final int tryAcquireShared(int unused) {
// 獲取當前線程
Thread current = Thread.currentThread();
// 獲取同步狀態
int c = getState();
// 如果寫鎖被另外一個線程獲取,返回-1
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
// 讀鎖被獲取的數量
int r = sharedCount(c);
if (!readerShouldBlock() &&
r < MAX_COUNT &&
compareAndSetState(c, c + SHARED_UNIT)) {
if (r == 0) {
// 讀鎖未被任何線程獲取
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {
// 獲取鎖的線程是第一次獲取讀鎖的線程
// 將線程(第一次獲取讀鎖的)獲取讀鎖的次數加1
firstReaderHoldCount++;
} else {
// 獲取鎖的線程不是第一次獲取讀鎖的線程
// 將線程(所有)獲取讀鎖的次數的總和加1
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);
}
這裏同樣需要注意readerShouldBlock方法,判斷該線程是否有資格獲取鎖,與ReentrantReadWriteLock的公平與否有關。與寫鎖的獲取不同的是,非公平性的鎖競爭並不是所有線程都可以有資格獲取讀鎖。如果在同步隊列中第一個阻塞的節點是獨佔式節點,即線程獲取寫鎖,此時讀鎖沒有資格獲取鎖。這是爲了避免寫鎖飢餓,在使用ReentrantReadWriteLock的過程中,讀的操作比寫的操作多,如果不對其進行限制,線程將很難獲取到寫鎖。
final boolean readerShouldBlock() {
return apparentlyFirstQueuedIsExclusive();
}
final boolean apparentlyFirstQueuedIsExclusive() {
Node h, s;
return (h = head) != null &&
(s = h.next) != null &&
// 判斷是否爲獨佔式獲取鎖,即寫鎖
!s.isShared() &&
s.thread != null;
}
讀鎖的釋放
ReentrantReadWriteLock會調用自定義同步組件的tryReleaseShared方法釋放同步狀態。
static final int SHARED_SHIFT = 16;
static final int SHARED_UNIT = (1 << SHARED_SHIFT);
protected final boolean tryReleaseShared(int unused) {
// 獲取當前線程
Thread current = Thread.currentThread();
// 判斷線程是否爲第一次獲取讀鎖的線程
if (firstReader == current) {
if (firstReaderHoldCount == 1)
firstReader = null;
else
firstReaderHoldCount--;
} else {
// 將線程(所有)獲取讀鎖的數量減1
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();
// 同步狀態中讀鎖的數量減1
int nextc = c - SHARED_UNIT;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}