锁系列文章:
java多线程系列7-AbstractQueuedSynchronizer源码分析
java多线程系列8-ReentrantLock源码分析
java多线程系列9-共享锁Semaphore源码解析
1.ReentrantReadWriteLock介绍
ReentrantReadWriteLock是基于AQS实现的另外一个比较常见的读写锁,适用多读少写的场景。也分为公平锁和非公平锁。和之前分析的锁不同的是,它两把锁,一把读锁,一把写锁。
2.读锁
(1)lock方法
public void lock() {
this.sync.acquireShared(1);
}
(2)acquireShared是AQS中方法,之前分析过
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
(3)tryAcquireShared
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);
//这里分为公平锁和公平锁
//公平锁判断等待队列中是否有线程在等待
//非公平锁判断等待队列中第一个是否是写线程
if (!readerShouldBlock() &&
r < MAX_COUNT &&
compareAndSetState(c, c + SHARED_UNIT)) {
//下面是计算锁数量的,不是我们重点,不详细分析
if (r == 0) {
firstReader = current;
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;
}
//第一次获取读锁失败,有两种情况:
//1)没有写锁被占用时,尝试通过一次CAS去获取锁时,更新失败(说明有其他读锁在申请)
//2)当前线程占有写锁,并且有其他写锁在当前线程的下一个节点等待获取写锁,除非当前线程的下一个节点被取消,否则fullTryAcquireShared也获取不到读锁
return fullTryAcquireShared(current);
}
(4)公平锁和非公平锁readerShouldBlock
非公平锁
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();
}
公平锁
final boolean readerShouldBlock() {
//等待队列中已有线程在等待
return hasQueuedPredecessors();
}
(5)fullTryAcquireShared
final int fullTryAcquireShared(Thread current) {
/*
* This code is in part redundant with that in
* tryAcquireShared but is simpler overall by not
* complicating tryAcquireShared with interactions between
* retries and lazily reading hold counts.
*/
HoldCounter rh = null;
for (;;) {
int c = getState();
//如果当前线程不是写锁的持有者,直接返回-1,结束尝试获取读锁,需要排队去申请读锁
if (exclusiveCount(c) != 0) {
if (getExclusiveOwnerThread() != current)
return -1;
// else we hold the exclusive lock; blocking here
// would cause deadlock.
//如果需要阻塞,说明除了当前线程持有写锁外,还有其他线程已经排队在申请写锁,故,即使申请读锁的线程已经持有写锁(写锁内部再次申请读锁,俗称锁降级)还是会失败
//因为有其他线程也在申请写锁,此时,只能结束本次申请读锁的请求,转而去排队,否则,将造成死锁。
} 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;
}
}
}
(6)doAcquireShared是AQS中方法,这里不再分析
3.写锁
(1)lock方法
public void lock() {
this.sync.acquire(1);
}
(2)acquire方法是AQS中的方法,这里不再分析
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
(3)tryAcquire方法
protected final boolean tryAcquire(int acquires) {
/*
* Walkthrough:
* 1. If read count nonzero or write count nonzero
* and owner is a different thread, fail.
* 2. If count would saturate, fail. (This can only
* happen if count is already nonzero.)
* 3. Otherwise, this thread is eligible for lock if
* it is either a reentrant acquire or
* queue policy allows it. If so, update state
* and set owner.
*/
Thread current = Thread.currentThread();
int c = getState();
int w = exclusiveCount(c);
//已有线程获得锁,可能是写锁也可能是读锁
if (c != 0) {
// (Note: if c != 0 and w == 0 then shared count != 0)
//如果是读锁,直接返回;如果是写锁,并且不是当前线程,也退出
if (w == 0 || current != getExclusiveOwnerThread())
return false;
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// Reentrant acquire
//写锁重入次数
setState(c + acquires);
return true;
}
//非公平锁不会阻塞,公平锁需判断等待队列中是否已有线程在等待
if (writerShouldBlock() ||
//设值state状态
!compareAndSetState(c, c + acquires))
return false;
//获得锁线程设为自己
setExclusiveOwnerThread(current);
return true;
}
(4)公平锁和非公平锁writerShouldBlock
非公平锁
final boolean writerShouldBlock() {
return false; // writers can always barge
}
公平锁
final boolean writerShouldBlock() {
return hasQueuedPredecessors();
}
(5)获取锁失败则加入等待队列,这是AQS中方法不再分析
4.总结
获得读锁大概流程:
- 首先判断当前是否有写锁,如果有,并且不是当前线程,则失败加入同步队列
- 如果没有读锁,或者是当前线程持有写锁,判断获得读锁是否需要阻塞
- 公平锁下,如果已有线程在同步队列下等待,则需阻塞,加入同步队列
- 非公平锁下,判断同步队列第一个线程是否是写锁,是则需阻塞,加入同步队列
- 以上都不满足,则可以获得读锁
写锁的大概流程
- 判断当前是否有读锁,如果有,则阻塞加入同步队列
- 如果没有读锁,判断获得写错是否是当前线程,是则重入,不是则阻塞加入同步队列
- 如果当前没有锁,判断需不要阻塞,非公平模式下不需要阻塞,公平模式下,需判断同步队列中是否已有线程在等待
- 如果需要阻塞,加入入同步队列。不需要阻塞,获得写锁,将锁持有线程设置为自己