二十四、ReentrantReadWriteLock之读锁重入流程

58 阅读3分钟

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对象。

重入的流程:

  1. 判断当前线程是否是第一个拿到读锁资源,如果是,直接将firstReader以及firstReaderHoldCount设置为当前线程的信息
  2. 判断当前线程是否是firstReader,如果是,直接对firstReaderHoldCount++即可
  3. 如果以上条件不满足,判断cachedHoldCounter是否是当前线程
    1. 如果不是,获取当前线程的重入次数,将cachedHoldCounter设置为当前线程
    2. 如果是,判断当前重入次数是否为0,如果是,重新设置当前线程的锁重入信息到readHolds中
    3. 前面两种情况最后都做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);
}