Java并发编程ReentrantReadWriteLock加读锁流程

2023-05-19 17:05:01 编程 并发 流程

正文

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))
                cachedHoldCounter = rh = readHolds.get();
            else if (rh.count == 0)
                readHolds.set(rh);
            rh.count++;
        }
        return 1;
    }
    return fullTryAcquireShared(current);
}

上面是尝试加读锁流程的代码,既然这篇是番外篇,那就不按正常流程一点一点去分析了,着重拿出一部分来分析一下。ReentrantReadWriteLockReentrantLock相比,除了多了读写锁之外,还增加了很多属性,比如firstReaderfirstReaderHoldCountcachedHoldCounter......那我们这篇文章就介绍一下这些新属性的含义以及上面代码中加锁成功后的处理。

属性介绍

static final class HoldCounter {
    int count = 0;
    final long tid = getThreadId(Thread.currentThread());
}

HoldCount类型用来存储线程ID和线程持有的读锁数量

private transient ThreadLocalHoldCounter readHolds;
static final class ThreadLocalHoldCounter
    extends ThreadLocal&lt;HoldCounter&gt; {
    public HoldCounter initialValue() {
        return new HoldCounter();
    }
}

readHolds通过ThreadLocal在线程本地存储了一个HoldCounter对象,表示当前线程持有的读锁重入数量,主要是为了方便在发生重入或者释放锁时,分别计算每个线程持有的读锁数量。

private transient HoldCounter cachedHoldCounter;

cachedHoldCounter存储的是最后一个获取读锁成功的线程持有的读锁数量。但是如果只有一个线程获取读锁,会使用firstReaderfirstReaderHoldCount来记录线程持有读锁数量,只有获取读锁的线程数大于1时才会用cachedHoldCounter存储最后线程持有的读锁数量。

private transient Thread firstReader = null;

第一个获取读锁的线程,确切地说是把读锁数量从0改成1的线程,并且当前还没有释放锁。如果第一个线程释放了锁,就会把firstReader设为null,只有当所有读锁释放之后,下一个获取读锁成功的线程就成为firstReader

private transient int firstReaderHoldCount;

第一个获取读锁的线程持有读锁的数量。

加锁成功处理

int r = sharedCount(c);
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++;
}

这里截取加锁成功之后处理的代码来分析下对这些属性的操作。

  • if (r == 0)表示共享锁数量为0,当前线程就是第一个获取读锁成功的线程,所以firstReaderfirstReaderHoldCount记录的就是当前线程。
  • 如果读锁数量不是0,但是当前线程是第一个线程,那就直接在原来数量基础上进行累加 firstReaderHoldCount++;
  • 如果读锁数量不为0,而且当前线程也不是第一个线程,这时就需要用到cachedHoldCounter了。
    • rh == null表示当前线程是第二个线程,rh.tid != getThreadId(current)表示当前线程至少是第三个线程(这里不考虑重入情况,只考虑当前线程第一次获取读锁成功),两个条件合起来可以理解为之前缓存的最后一个获取读锁成功的线程不是当前线程,所以就需要更新为当前线程cachedHoldCounter = rh = readHolds.get()
    • 如果之前缓存的最后一个线程是当前线程,那么就会有一个特殊情况rh.count == 0,这里可以理解为一个线程释放了读锁之后又重新获取了读锁,释放完所有锁时,为了防止内存泄漏会调用readHolds.remove()清除线程本地存储的信息,而现在加锁成功了就需要在线程本地重新记录持有锁的数量,既然缓存的就是当前线程的,那就直接用缓存来更新到线程本地就可以了。

以上就是Java并发编程ReentrantReadWriteLock番外的详细内容,更多关于java并发ReentrantReadWriteLock的资料请关注其它相关文章!

相关文章