Java併發包中ReadWriteLock是一個接口,主要有兩個方法
public interface ReadWriteLock {
/**
* 返回讀鎖
*/
Lock readLock();
/**
* 返回寫鎖
*/
Lock writeLock();
}
Java併發庫中ReetrantReadWriteLock實現了ReadWriteLock接口,並添加了可重入的特性
構造方法
ReentrantReadWriteLock有兩個構造方法
public ReentrantReadWriteLock() {
this(false);
}
public ReentrantReadWriteLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
readerLock = new ReadLock(this);
writerLock = new WriteLock(this);
}
默認的構造方法使用的是非公平模式,創建的Sync是NonfairSync對象,然後初始化讀鎖和寫鎖。
同步器
FairSync的實現:
static final class FairSync extends Sync {
private static final long serialVersionUID = -2274990926593161451L;
final boolean writerShouldBlock() {
return hasQueuedPredecessors();
}
final boolean readerShouldBlock() {
return hasQueuedPredecessors();
}
}
再來看NonfairSync的實現:
static final class NonfairSync extends Sync {
private static final long serialVersionUID = -8159625535654395037L;
final boolean writerShouldBlock() {
return false; // writers can always barge
}
final boolean readerShouldBlock() {
/* As a heuristic to avoid indefinite writer starvation,
* block if the thread that momentarily appears to be head
* of queue, if one exists, is a waiting writer. This is
* only a probabilistic effect since a new reader will not
* block if there is a waiting writer behind other enabled
* readers that have not yet drained from the queue.
*/
return apparentlyFirstQueuedIsExclusive();
}
}
非公平模式下,writerShouldBlock直接返回false,說明不需要阻塞;而readShouldBlock調用了apparentFirstQueuedIsExcluisve()方法。
繼承AQS的類都需要使用state變量代表某種資源,ReentrantReadWriteLock中的state代表了讀鎖的數量和寫鎖的持有與否,整個結構如下:
state的高16位代表讀鎖的個數,低16位代表寫鎖的狀態
獲取鎖
讀鎖的獲取
當需要使用讀鎖時,首先調用lock方法
public void lock() {
sync.acquireShared(1);
}
AQS的acquireShared方法
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
當tryAcquireShared()方法小於0時,那麼會執行doAcquireShared方法將該線程加入到等待隊列中。
protected final int tryAcquireShared(int unused) {
/*
* Walkthrough:
* 1. If write lock held by another thread, fail.
* 2. Otherwise, this thread is eligible for
* lock wrt state, so ask if it should block
* because of queue policy. If not, try
* to grant by CASing state and updating count.
* Note that step does not check for reentrant
* acquires, which is postponed to full version
* to avoid having to check hold count in
* the more typical non-reentrant case.
* 3. If step 2 fails either because thread
* apparently not eligible or CAS fails or count
* saturated, chain to version with full retry loop.
*/
Thread current = Thread.currentThread();
int c = getState();
//如果當前有寫線程並且本線程不是寫線程,不符合重入,失敗
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
//得到讀鎖的個數
int r = sharedCount(c);
//如果讀不應該阻塞並且讀鎖的個數小於最大值65535,並且可以成功更新狀態值,成功
if (!readerShouldBlock() &&
r < MAX_COUNT &&
compareAndSetState(c, c + SHARED_UNIT)) {
//如果當前讀鎖爲0
if (r == 0) {
//第一個讀線程就是當前線程
firstReader = current;
firstReaderHoldCount = 1;
}
//如果當前線程重入了,記錄firstReaderHoldCount
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);
}
fullTryAcquiredShared方法
final int fullTryAcquireShared(Thread current) {
HoldCounter rh = null;
for (;;) {
int c = getState();
//一旦有別的線程獲得了寫鎖,返回-1,失敗
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");
//如果成功更改狀態,成功返回
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;
}
}
}