关于ReentrantReadWriteLock的一些问题
场景
- 在使用此Lock时应特别注意: 读 ——> 写 ——> 读 交替使用的场景
- 在工作使用时,该场景是经常出现的,在非公平锁的
- 若干个读写线程抢占读写锁
- 读线程手脚快,优先抢占到读锁(其中少数线程任务较重,执行时间较长)
- 写线程随即尝试获取写锁,未成功,进入双列表进行等待
- 随后读线程也进来了,要去拿读锁
关键 JUC 的实现代码
ReentrantReadWriteLock.ReadLock.clas [行:727]
public void lock() {
sync.acquireShared(1);
}
ReentrantReadWriteLock.Sync.class [行:448], 所有线程要获取锁时,需要通过调用readerShouldBlock的方法判断当前线程是否需要被阻塞。
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;
}
return fullTryAcquireShared(current);
}
ReentrantReadWriteLock.NonfairSync.class [行:671], 此处可见,写锁在尝试获取锁时,是不会阻塞的,而读锁取决于,线程等待队列的队头的下一个等待线程是不是获取写锁,如果是,则将阻塞当前读锁的获取,这是非公平锁的一个特性的重要体现,此处也证明了,写锁优先权。
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();
}
final boolean apparentlyFirstQueuedIsExclusive() {
Node h, s;
return (h = head) != null &&
(s = h.next) != null &&
!s.isShared() &&
s.thread != null;
}
总结
- 1.使用ReentrantReadWriteLock时,读锁使用tryLock(xx,xx)的方式获取锁,并处理超时的逻辑
- 2.写锁由于具有优先权,可以使用lock, 但是应使用
writeLock.lock();
readLock.lock();
writeLock.unlock();
readLock.unlock();
之所以上面的可以,是利用写锁具有优先权,同时,当当前线程已有写锁,自然就可以使用读锁, 这样就可以防止多个读锁与写锁在多个不同线程中交替出现,以此防止死锁问题。