我正在参加「掘金·启航计划」
ReentrantReadWriteLock 介绍
ReentrantReadWriteLock 读写锁主要使用在读多写少的场景中,读写锁允许同一时刻被多个读线程访问,但是在写线程访问时,所有的读线程和其他的写线程都会被阻塞。
ReentrantReadWriteLock 还是基于 AQS 实现的。很多功能的实现和 ReentrantLock 类似,还是基于 AQS 的 state 来确定当前线程是否拿到锁资源。
在 ReentrantReadWriteLock 中,将 state 的高 16 位作为读锁的标识,而将低 16 位作为写锁的标识。
其中,读写锁有以下几种规则:
-
读读操作是共享的。
-
写写操作是互斥的。
-
读写操作是互斥的。
-
写读操作是互斥的。
-
单个线程获取写锁后,再次获取读锁,可以拿到。(写读可重入)
-
单个线程获取读锁后,再次获取写锁,拿不到,因为读锁是共享锁,对于其他线程来说是不安全的。(读写不可重入)
ReentrantReadWriteLock 写锁
写锁加锁操作
调用内部类 sync 中的 acquire() 方法。
public void lock() {
// 调用内部类sync中的acquire()方法
sync.acquire(1);
}
首先调用 tryAcquire() 方法,尝试获取锁资源,如果成功,则 acquire() 方法结束。如果获取失败,则执行 addWaiter() 方法和 acquireQueued() 方法。
public final void acquire(int arg) {
// 首先调用tryAcquire()方法,尝试获取锁资源,如果成功,则acquire方法结束
// 如果获取失败,则执行addWaiter()方法和acquireQueued()方法
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
protected final boolean tryAcquire(int acquires) {
// 获取当前线程
Thread current = Thread.currentThread();
// 获取AQS中的state值
int c = getState();
// 这里将state和EXCLUSIVE_MASK进行与运算,获取写锁状态
int w = exclusiveCount(c);
// 如果c不等于0,说明有线程持有写锁或者读锁
if (c != 0) {
// (Note: if c != 0 and w == 0 then shared count != 0)
// 如果w == 0是true,说明没有线程持有写锁,说明有线程持有读锁,读锁和写锁互斥,返回false
// 如果w == 0是false,说明有线程持有写锁,写锁可重入,判断当前持有锁的线程是不是获取锁的线程,如果不是,返回false
if (w == 0 || current != getExclusiveOwnerThread())
return false;
// 程序走到这个if代码块,说明w != 0,持有锁的线程为当前线程
// 这里查看更新state低位时是否超过最大值,超过则抛出异常
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// Reentrant acquire
// 更新AQS的state值
setState(c + acquires);
return true;
}
// 如果c == 0,说明没有线程持有锁
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))
return false;
setExclusiveOwnerThread(current);
return true;
}
static final int SHARED_SHIFT = 16;
// 1左移16为 00000000 00000001 00000000 00000000
static final int SHARED_UNIT = (1 << SHARED_SHIFT);
// 00000000 00000000 11111111 11111111
static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1;
// 00000000 00000000 11111111 11111111
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
exclusiveCount() 方法主要通过 与 运算获取写锁状态。
其中 addWaiter() 和 acquireQueued() 方法和 ReentrentLock 中一样,这里就不重复分析了。
写锁释放锁操作
public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
// 这里只有tryRelease方法是读写锁重新实现的,其他方法和ReentrantLock一样
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
protected final boolean tryRelease(int releases) {
// 判断释放锁的线程是不是当前持有锁的线程
if (!isHeldExclusively())
// 如果不是,抛出异常
throw new IllegalMonitorStateException();
// state - 1
int nextc = getState() - releases;
// 获取state低16为状态
boolean free = exclusiveCount(nextc) == 0;
// 如果state低16位为0
if (free)
// 将持有互斥锁的线程信息置为null
setExclusiveOwnerThread(null);
// 设置state
setState(nextc);
return free;
}
ReentrantReadWriteLock 读锁
读锁加锁操作
public void lock() {
sync.acquireShared(1);
}
public final void acquireShared(int arg) {
// tryAcquireShared方法尝试获取锁资源,获取到返回1,没获取到返回-1
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
protected final int tryAcquireShared(int unused) {
/*
* Walkthrough:
* 1. If write lock held by another thread, fail.
* 2. Otherwise, this thread is eligible for
* lock wrt state, so ask if it should block
* because of queue policy. If not, try
* to grant by CASing state and updating count.
* Note that step does not check for reentrant
* acquires, which is postponed to full version
* to avoid having to check hold count in
* the more typical non-reentrant case.
* 3. If step 2 fails either because thread
* apparently not eligible or CAS fails or count
* saturated, chain to version with full retry loop.
*/
// 获取当前线程
Thread current = Thread.currentThread();
// 获取state
int c = getState();
// 如果exclusiveCount(c) != 0是true,说明存在写锁,但是持有锁的线程不是当前线程没办法重入,所以获取锁资源失败返回-1
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
// 获取读锁信息
int r = sharedCount(c);
// 1. 如果是公平锁,有线程排队,返回true,直接跳过if代码块,有线程排队,返回false
// 2. 如果是非公平锁:正常的逻辑是直接抢。
// 因为是读锁,每次抢占只要CAS成功,必然成功。
// 但是,这样就会出现问题,写操作将无法在读锁的情况抢占资源,导致写线程饥饿,造成一直阻塞。
// 因此,非公平锁会查看next是否是写锁的,如果是,返回true,直接跳过if代码块,如果不是返回false
if (!readerShouldBlock() &&
r < MAX_COUNT &&
// 以CAS的方式对state的高16为加1
compareAndSetState(c, c + SHARED_UNIT)) {
// 这里表示已经成功获取锁资源
// 如果读锁状态为0,说明当前没有线程持有读锁
if (r == 0) {
// 那么将firstReader设置为当前线程,表示第一个拿到锁资源的线程
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {
// 如果firstReader是当前线程,说明是读锁重入,count加1,记录重入次数
firstReaderHoldCount++;
} else {
// 如果不是第一个获取锁的线程
HoldCounter rh = cachedHoldCounter;
// 如果rh == null,说明当前线程是第二个拿到读锁的
// 或者是之前有最后一个,但是不是当前线程
// 将当前线程设置成最后一个
if (rh == null || rh.tid != getThreadId(current))
cachedHoldCounter = rh = readHolds.get();
// 之前拿过,先在如果是0
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
}
return 1;
}
return fullTryAcquireShared(current);
}
static int sharedCount(int c) { return c >>> SHARED_SHIFT; }
static final class HoldCounter {
int count = 0;
// Use id, not reference, to avoid garbage retention
final long tid = getThreadId(Thread.currentThread());
}
/**
* ThreadLocal subclass. Easiest to explicitly define for sake
* of deserialization mechanics.
*/
static final class ThreadLocalHoldCounter
extends ThreadLocal<HoldCounter> {
public HoldCounter initialValue() {
return new HoldCounter();
}
}
每个读操作的线程,在获取读锁时,都需要开辟一个 ThreadLocal。读写锁为了优化这个事情,做了两方面操作:
- 第一个拿到读锁的线程,不用
ThreadLocal记录重入次数,在读写锁内有一个firstReader属性记录重入次数。 - 还记录了最后一个拿到读锁的线程的重入次数,交给
cachedHoldCounter属性记录,可以避免频繁锁重入时,从ThreadLocal中获取。
final int fullTryAcquireShared(Thread current) {
// 通过tryAcquireShared没拿到锁资源,也没返回-1,就走这
/*
* This code is in part redundant with that in
* tryAcquireShared but is simpler overall by not
* complicating tryAcquireShared with interactions between
* retries and lazily reading hold counts.
*/
HoldCounter rh = null;
for (;;) {
// 获取state
int c = getState();
// 如果有写锁但是不是当前线程持有,不可重入,直接返回-1
if (exclusiveCount(c) != 0) {
if (getExclusiveOwnerThread() != current)
return -1;
// else we hold the exclusive lock; blocking here
// would cause deadlock.
} else if (readerShouldBlock()) {
// 如果是公平锁,有排队,进入代码块,没有排队,跳过
// 如果是非公平锁,判断head的next是写吗,如果是,进入代码块,如果不是,跳过
// 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();
}
}
// 如果当前线程的次数是0,那么就不是重入操作
if (rh.count == 0)
// 返回-1,阻塞等待
return -1;
}
}
// 超过最大值,抛异常
if (sharedCount(c) == MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// CAS竞争锁资源
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;
}
}
}
没获取到锁资源,执行下面方法,将当前线程加入队列,进行阻塞等待:
private void doAcquireShared(int arg) {
// 将当前线程封装为Node,共享锁模式,并加入队列
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
// 获取当前线程Node的前一个节点
final Node p = node.predecessor();
// 如果前一个节点是head的话,再次尝试获取锁资源
if (p == head) {
int r = tryAcquireShared(arg);
// 如果r >= 0,说明获取锁资源成功,设置Head,唤醒当前线程Node后面要获取读锁的线程
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}