ReentrantReadWriteLock类的大体结构
在看完 ReentrantReadWriteLock 的源码后,发现主要还是通过它的内部类 Sync(当然咯,还有其它的内部类) 实现的,而 Sync 又继承了 AbstractQueuedSynchronizer 类;同时还通过 ThreadLocalHoldCounter(此类继承了ThreadLocal)来记录HoldCounter 对象。所以,在阅读源码时,建议最好掌握以下知识点,不然可能会有点儿懵:
知识 |
---|
AQS 的实现 |
ThreadLocal 的实现 |
首先先创建一个 ReentrantReadWriteLock 对象:
ReadWriteLock lock = new ReentrantReadWriteLock(false);
重点在 sync 会根据传入的 fair(公平) 值来确定,这里传入为 false ,所以 " sync = new FairSync()"
public ReentrantReadWriteLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
readerLock = new ReadLock(this);
writerLock = new WriteLock(this);
}
然后再看读锁的实现 :
读锁的实现
加锁和释放锁操作为:
lock.readLock().lock();
lock.readLock().unlock();
具体实现:
- 外部调用 lock 方法
public void lock() {
sync.acquireShared(1);
}
- 调用内部类 Sync 的 acquireShared 方法,且传入参数 为 1;
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
- 在第2步中判断 tryAcquireShared(arg) < 0 ,其中参数 arg 为 1,执行 tryAcquireShared 方法
第一次上读锁时,获取成功,随后更新对应的状态记录。但如果返回 -1 呢(即已经被其它线程上
了写锁)?流程看3.1.x ;如果返回的是 fullTryAcquireShared(current) , 流程看 3.2.x ;
// 位置 ReentrantReadWriteLock 源码 448 行
protected final int tryAcquireShared(int unused) { //此时 unused 的值为 1
/*
* 1. 如果另一个线程持有写锁,则失败
* 2. 除此之外,该线程符合 wrt 状态,因此需要询问(ask)由于队列策略是否应该阻塞
* 如果不阻塞,尝试 CASing 状态授予许可并更新计数(count).注意:该步骤步检查重入
* 获取,推迟到完整版本(这里应该指的是子类),已避免在更典型的非重入的情况下必须
* 检查计数(count)
* 3. 如果第二步失败,因为线程不符合条件或 CAS 失败或计数(count)饱和,链接到完
* 整版本重试
*/
Thread current = Thread.currentThread(); // 获取当前线程
int c = getState(); // 获取状态值
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1; // 写锁计数不为0,并且上写锁线程不是当前线程(就是被其它线程上了写锁),直接返回 -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);
}
3.1.1 此时 tryAcquireShared 返回的是 -1,即已被其它线程上了写锁,则在 2 中满足判断,
进入if代码块,执行代码: doAcquireShared(arg); 参数 arg 为 1
/**
* Acquires in shared uninterruptible mode
*/
// 代码位置:AQS 948行
private void doAcquireShared(int arg) {
final Node node = addWaiter(Node.SHARED); //添加到同步队列中
boolean failed = true;
try {
boolean interrupted = false;
for (;;) { // 死循环
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg); // 再次执行 tryAcquireShared 方法
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrxunhupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
3.1.2 若执行的是 fullTryAcquireShared 方法
/**
* Full version of acquire for reads, that handles CAS misses
* and reentrant reads not dealt with in tryAcquireShared.
* 完整版本的获取读取,可处理 tryAcquireShared 中未处理的 CAS
* 丢失和重入读取
*/
final int fullTryAcquireShared(Thread current) {
HoldCounter rh = null;
for (;;) { // 死循环
int c = getState(); // 获取状态值
if (exclusiveCount(c) != 0) {
if (getExclusiveOwnerThread() != current)
return -1; // 写锁不为0,并且所属不是当前线程,即被其它线程加了写锁,直接返回-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; // 返回 -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;
}
}
}
所以,读锁的是:
**注:① 这里的 “小于0入队” 和 “大于0加锁成功” 描述不是很精确,因为在入队后仍然是死循环,其中仍不断进行尝试执行 tryAcquireShared 方法 ② 整体执行流程是这样的,加锁 和 入队后的代码具体实现需要自己去看了,这里再画的话图会非常小 **
上面这个细了,感觉图会有点儿小,省一步看看图能否会大一些:
写锁的实现
readWriteLock.writeLock().lock(); // 加锁
readWriteLock.writeLock().lock(); // 释放锁
- 首先仍是 外部调用 lock() 方法
public void lock() {
sync.acquire(1);
}
- 然后调用 acquire 方法
public final void acquire(int arg) {
if (!tryAcquire(arg) && // 步骤 ①
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) // 步骤 ②
selfInterrupt();
}
- 执行步骤 ① 方法 tryAcquire()
protected final boolean tryAcquire(int acquires) {
/**
* 1. 如果读计数为非零或写入计数为非零,并且所有者线程是另一个线程,则失败(代表已上锁)
* 2. 如果计数饱和,则失败(只有在count已经不为0 的情况下才可以发生)
* 3. 除此之外,如果该线程是允许可重入获取策略的话,则有资格进行上锁。如果是这样,请更
* 新状态并设置所有者
*/
Thread current = Thread.currentThread(); // 获取当前线程
int c = getState(); // 获取状态值
int w = exclusiveCount(c); // 获取写锁的计数
if (c != 0) { // c != 0 为true的话,代表已上锁,但上的是读锁还是写锁无法确定
// (Note: if c != 0 and w == 0 then shared count != 0)
// 注意这里的条件是 || ,也就是说
// 如果: w==0为true即代表了上的是读锁,后面的条件不会再进行判断
// 如果: w==0为false,即代表已经上了写锁,若 current!=getExclusiveOwnerThread()为true代表
// 非重入,即此时被其它线程加了写锁,直接返回
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); // 重入情况,不许通过CAS,因为已经上锁了
return true;
}
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires)) // 非重入情况,需要通过CAS来进行设置
return false;
setExclusiveOwnerThread(current);
return true;
}
- 如果 tryAcquire 返回 false 的话,说明加锁失败了,执行步骤②的 acquireQueued()方法,代码如下,再死循环中
仍不断去执行 tryAcquire() 方法
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) { //死循环
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) { // 仍不断执行 tryAcquire 方法
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
实验:在上面的源码中,分析看到,若 A 线程加了读锁后(不释放读锁),A线程再加写锁会直接返回false,然后进入acquireQueued方法中,在死循环中不断尝试,真的是这样的嘛?
实验代码如下:
@Test
public void ReentrantReadWriteLockTest2() throws Exception{
ReadWriteLock lock = new ReentrantReadWriteLock(false); // 创建 对象
lock.readLock().lock(); // 先加读锁
System.out.println("加读锁 ....."); // 为了能看到结果
lock.writeLock().lock();
System.out.println("加写锁.....");
lock.readLock().lock(); //释放读锁
lock.writeLock().lock(); // 释放写锁
}
实验结果如下:
实验结果分析:
**实验结果和在代码分析中的一样,当加了读锁后(无论是当前线程还是其它线程加的读锁),是无法再加写锁的,加写锁会不断进行尝试,直到释放了读锁,加写锁才能成功。当然咯,这个实验是不完整的,完整的实验还要加上 “加读锁后立即释放读锁,再加写锁,释放写锁”但这个实验我觉得就没有必要浪费笔墨了 **
这里就不对公平与不公平进行分析了,因为我之前的 ReentrantLock 中已经分析过了,虽然这里代码上会有些不一样,但都只是一个壳子,基本上都是换汤不换药的