/**
* 写锁获取过程
* @param acquires
* @return
*/
protected final boolean tryAcquire(int acquires) {
Thread current = Thread.currentThread();
int c = getState(); //获取线程状态
int w = exclusiveCount(c); //获取写锁状态,及c的低16位表示的值
if (c != 0) { //c != 0 表示有线程持有锁,可能是读锁也可能是写锁
//这个判断返回false,目的是过滤掉读锁持有线程及写锁持有线程不是重入写锁的情况(读写锁是互斥的,写写锁是互斥的)
if (w == 0 || current != getExclusiveOwnerThread())
return false; //命中了上面的情况
if (w + exclusiveCount(acquires) > MAX_COUNT) //写锁用state的低16位表示,大于这个值视为非法
throw new Error("Maximum lock count exceeded");
// Reentrant acquire
setState(c + acquires); //走到这里的线程必然是写锁,且是写锁重入,所以这个操作不需要原子操作
return true; //获取锁成功
}
//走到这里表示目前没有线程持有锁,这里会有锁竞争存在,状态的修改需要原子环境
//writerShouldBlock() 写锁此方法一直返回false
//此判断过滤掉竞争锁失败的线程
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))
return false;
//设置当前线程为独占线程
setExclusiveOwnerThread(current);
return true;
}
写锁获取重要分两种情况进行
一 state !=0 主状态不为零,这种情况能够获取写锁的唯一条件是,当前持有锁的线程是写锁,且是写锁重入,其他情况该线程都不可能获得锁
二 state = 0 表示没有任何线程持有锁,当前线程在竞争条件下通过原子操作获取锁,是否成要看运气(不成功则进入阻塞队列)
整个写锁获取围绕上面两个条件进行
/**
* 写锁的释放过程
* @param releases
* @return
*/
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; //true表示所有写线程都释放完毕,反正还有重入锁未释放
}
写锁的释放很简单,第一步判断进入释放方法的线程是否是当前独占锁(进入写锁释放的方法必然要是当前独占锁)
由于存在锁重入情况,当所有重入锁都释放后,返回成功唤醒阻塞队列排在前面的线程,否则防护false 不唤醒任何阻塞队列中的线程
/**
* 读锁的释放过程
* 1 读锁与读锁之间是共享锁,即一个读锁持有锁,后面的读锁仍然可以获取读锁
* 2读锁和写锁的关系,读锁和写锁之间是互斥锁,但在获取读锁时,当前线程如果持有写锁,仍然可以获取读锁,当前线程持有读锁则不能获取写锁
* 该思想贯穿整个读锁获取方法
* @param unused
* @return
*/
protected final int tryAcquireShared(int unused) {
Thread current = Thread.currentThread();
int c = getState(); //获取读写锁总体状态
//写线程持有写锁,且不是持有写锁的线程进行读操作,违反上面介绍的第二条(2)
// 除了这种情况,其他情况线程都可以竞争获取线程
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
int r = sharedCount(c); //获取当前持有读锁的数量(state的高16位表示的数值)
//走到这里都是可以竞争锁的线程,(当然执行这个方法的线程都是为了获取读锁,这句话是多余的)
//readerShouldBlock读锁可以获取线程,阻塞队列head.next不是写锁,r < MAX_COUNT 读锁的数量未达到最大值
// compareAndSetState(c, c + SHARED_UNIT)当前线程获取读锁,状态变更成功
if (!readerShouldBlock() &&
r < MAX_COUNT &&
compareAndSetState(c, c + SHARED_UNIT)) {
//走到这里表示成获取线程
//该线程第一次获取读锁,未有重入情况
//firstReader,firstReaderHoldCount 记录第一次获取读锁的线程,主要作用是简单缓存
if (r == 0) {
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) { //第一次获取读锁的线程重入啦
firstReaderHoldCount++; //记录重入次数
} else {
//处理非第一次获取读锁的线程
//这里也是做缓存,优化性能
// cachedHoldCounter存储最后一次获取读锁的线程
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() 返回 true,2 种情况:
在 FairSync 中说的是 hasQueuedPredecessors(),即阻塞队列中有其他元素在等待锁。
也就是说,公平模式下,有人在排队呢,你新来的不能直接获取锁
在 NonFairSync 中说的是 apparentlyFirstQueuedIsExclusive(),即判断阻塞队列中 head 的第一个后继节点是否是来获取写锁的,如果是的话,让这个写锁先来,避免写锁饥饿。
作者给写锁定义了更高的优先级,所以如果碰上获取写锁的线程马上就要获取到锁了,获取读锁的线程不应该和它抢。
如果 head.next 不是来获取写锁的,那么可以随便抢,因为是非公平模式,大家比比 CAS 速度
compareAndSetState(c, c + SHARED_UNIT) 这里 CAS 失败,存在竞争。可能是和另一个读锁获取竞争,当然也可能是和另一个写锁获取操作竞争。
然后就会来到 fullTryAcquireShared 中再次尝试:
*/
/**
* 抓住再次获取锁的机会
* @param current
* @return
*/
final int fullTryAcquireShared(Thread current) {
ReentrantReadWriteLock.Sync.HoldCounter rh = null;
for (;;) { //模拟锁自旋操作
int c = getState();
//写锁被占用
if (exclusiveCount(c) != 0) {
if (getExclusiveOwnerThread() != current)
return -1;
//虽然head.next是写锁,但当前读锁是重入锁,就不用等了啦
} else if (readerShouldBlock()) {
//这里表示写锁不持有锁,下面只处理读锁重入的情况
if (firstReader == current) {
//读锁重入啦
} else {
if (rh == null) {
rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current)) {
rh = readHolds.get();
//不是重入线程,count= 0 是上面初始化添加的
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;
}
}
/**
* 读锁释放过程
* @param unused
* @return
*/
protected final boolean tryReleaseShared(int unused) {
Thread current = Thread.currentThread();
if (firstReader == current) { //释放第一次获取读锁的线程
// assert firstReaderHoldCount > 0;
if (firstReaderHoldCount == 1) //第一次获取读锁firstReader 重入释放完成,置为null
firstReader = null;
else
firstReaderHoldCount--; //自减重入次数
} else {
ReentrantReadWriteLock.Sync.HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
rh = readHolds.get(); //缓存中获取当前线程的重入信息
int count = rh.count; //当前持有读锁线程重入次数
if (count <= 1) { //当前读线程重入释放完成,移除缓存map
readHolds.remove();
if (count <= 0) //非法重入数记录
throw unmatchedUnlockException();
}
--rh.count; //重入数自减
}
for (;;) { //这里模拟线程自旋,保证compareAndSetState(c, nextc)必然会成功
int c = getState();
//读锁重入数自减后,变更读写锁state的状态
int nextc = c - SHARED_UNIT;
if (compareAndSetState(c, nextc)) //原子操作状态变更
// Releasing the read lock has no effect on readers,
// but it may allow waiting writers to proceed if
// both read and write locks are now free.
return nextc == 0;
}
}
Doug Lea 没有说写锁更高级,如果有线程持有读锁,那么写锁获取也需要等待。
不过从源码中也可以看出,确实会给写锁一些特殊照顾,如非公平模式下,为了提高吞吐量,lock 的时候会先 CAS 竞争一下,能成功就代表读锁获取成功了,但是如果发现 head.next 是获取写锁的线程,就不会去做 CAS 操作。
Doug Lea 将持有写锁的线程,去获取读锁的过程称为锁降级(Lock downgrading)。这样,此线程就既持有写锁又持有读锁。
但是,锁升级是不可以的。线程持有读锁的话,在没释放的情况下不能去获取写锁,因为会发生死锁。
回去看下写锁获取的源码:
protected final boolean tryAcquire(int acquires) {
Thread current = Thread.currentThread();
int c = getState();
int w = exclusiveCount(c);
if (c != 0) {
// 看下这里返回 false 的情况:
// c != 0 && w == 0: 写锁可用,但是有线程持有读锁(也可能是自己持有)
// c != 0 && w !=0 && current != getExclusiveOwnerThread(): 其他线程持有写锁
// 也就是说,只要有读锁或写锁被占用,这次就不能获取到写锁
if (w == 0 || current != getExclusiveOwnerThread())
return false;
...
}
...
}
仔细想想,如果线程 a 先获取了读锁,然后获取写锁,那么线程 a 就到阻塞队列休眠了,自己把自己弄休眠了,而且可能之后就没人去唤醒它了
精彩文章 https://www.javadoop.com/post/reentrant-read-write-lock