ReentrantReadWriteLock之读锁重入流程
简要概述:
ReentrantReadWriteLock的读锁可以重入,每个线程使用ThreadLocal存储重入次数,并且ReentrantReadWriteLock对读锁重入做了优化
static final class HoldCounter {
// 锁重入次数
int count = 0;
// 线程ID
final long tid = getThreadId(Thread.currentThread());
}
// ThreadLocalHoldCounter对象的get方法可以获取HoldCounter对象
static final class ThreadLocalHoldCounter
extends ThreadLocal<HoldCounter> {
public HoldCounter initialValue() {
return new HoldCounter();
}
}
每个线程的ThreadLocalMap中存储的数据,key是ThreadLocalHoldCounter对象,value是HoldCounter对象
调用ThreadLocalHoldCounter对象的get方法可以获取HoldCounter对象,HoldCounter对象的count属性存储着当前线程的锁重入次数
第一个获得读锁的线程:
第一个拿到读锁的线程不需要用ThreadLocal存储,内部提供了两个属性,firstReader存储拿到读锁的线程,firstReaderHoldCount记录重入次数
private transient Thread firstReader = null;
private transient int firstReaderHoldCount;
最后一个获得读锁的线程:
最后一个拿到读锁的线程使用cacheHoldCounter存储线程信息和重入次数
private transient HoldCounter cachedHoldCounter;
记录重入次数的核心思想:
ReentrantReadWriteLock对ThreadLocal做了封装,基于HoldCount对象存储重入次数。在其内部有一个count属性存储重入次数。读写锁只有唯一一个ThreadLocalHoldCounter对象,当线程调用其set方法时,会在线程自己的ThreadLocalMap中存储一个key是这个ThreadLocalHoldCounter对象,value是存储了自己锁重入次数的HoldCounter对象。当线程调用其get方法时,可以获取到存储了自己锁重入次数的HoldCounter对象。
重入的流程:
- 判断当前线程是否是第一个拿到读锁资源,如果是,直接将firstReader以及firstReaderHoldCount设置为当前线程的信息
- 判断当前线程是否是firstReader,如果是,直接对firstReaderHoldCount++即可
- 如果以上条件不满足,判断cachedHoldCounter是否是当前线程
- 如果不是,获取当前线程的重入次数,将cachedHoldCounter设置为当前线程
- 如果是,判断当前重入次数是否为0,如果是,重新设置当前线程的锁重入信息到readHolds中
- 前面两种情况最后都做count++
protected final int tryAcquireShared(int unused) {
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))
// 当前线程是最后一个拿到读锁的线程
// 线程信息存入cacheHoldCounter中
cachedHoldCounter = rh = readHolds.get();
else if (rh.count == 0)
// 进入这里说明当前线程是最后一个拿到读锁的线程
// 并且刚释放读锁,又获取读锁
// 由于将读锁全部释放的时候会调用ThreadLocal的remove方法
// 将当前线程的ThreadLocalMap中数据删除,所以这里需要将rh存入Map中
readHolds.set(rh);
rh.count++;
}
return 1;
}
// 没有拿到读锁资源
return fullTryAcquireShared(current);
}