ReentrantReadWriteLock源码解析
1. 结构
ReentrantReadWriteLock可重入的读写锁,顾名思义就是该重入锁中存在多种锁,1、重入锁;2、读锁;3、写锁;读锁、写锁中都存在着一个Sync同步器,该同步器又是重入锁的sync
下面是读写锁的基本结构
很清楚了,ReadLock 和 WriteLock 中的方法都是通过 Sync 这个类来实现的。Sync 是 AQS 的子类,然后再派生了公平模式和不公平模式。
2. 基础信息
static final int SHARED_SHIFT = 16;
// 最多支持65535个递归读取锁。
static final int SHARED_UNIT = (1 << SHARED_SHIFT);
// 最多支持65535个递归写入锁。
static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1;
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
/** Returns the number of shared holds represented in count */
// 读状态等于S >>> 16(无符号补0右移16位), 高16位,表示读锁获取次数
static int sharedCount(int c) { return c >>> SHARED_SHIFT; }
/** Returns the number of exclusive holds represented in count */
// 写状态等于 S & 0x0000FFFF(将高16位全部抹去),低16位,表示写锁获取次数
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
3. 获取锁
从它们调用的 Sync 方法,我们可以看到: ReadLock 使用了共享模式,WriteLock 使用了独占模式。
3.1 写锁尝试获取锁
-
java.util.concurrent.locks.ReentrantReadWriteLock.Sync#tryAcquire
:尝试获取写锁。因为要确保写锁的操作对读锁是可见的,如果在存在读锁的情况下允许获取写锁,那么那些已经获取读锁的其他线程可能就无法感知当前写线程的操作。因此只有等读锁完全释放后,写锁才能够被当前线程所获取,一旦写锁获取了,所有其他读、写线程均会被阻塞。protected final boolean tryAcquire(int acquires) { // 当前线程 Thread current = Thread.currentThread(); // 锁状态 int c = getState(); // 获取写锁数量 int w = exclusiveCount(c); if (c != 0) { // (Note: if c != 0 and w == 0 then shared count != 0) // 当前线程不是已经获取写锁的线程,或者存在读锁,那么获取失败 if (w == 0 || current != getExclusiveOwnerThread()) return false; // 超出最大范围 if (w + exclusiveCount(acquires) > MAX_COUNT) throw new Error("Maximum lock count exceeded"); // Reentrant acquire // 重入次数 + 1 setState(c + acquires); return true; } // c == 0, 锁目前没有被获取,是否需要阻塞 if (writerShouldBlock() || !compareAndSetState(c, c + acquires)) return false; // 设置获取锁的线程为当前线程 setExclusiveOwnerThread(current); return true; }
-
java.util.concurrent.locks.ReentrantReadWriteLock.FairSync#writerShouldBlock
:公平锁模式下,如果当前线程所在节点是 CLH 队列中的第一个有效节点,返回 true。final boolean writerShouldBlock() { return hasQueuedPredecessors(); } public final boolean hasQueuedPredecessors() { Node t = tail; // Read fields in reverse initialization order Node h = head; Node s; return h != t && ((s = h.next) == null || s.thread != Thread.currentThread()); }
-
java.util.concurrent.locks.ReentrantReadWriteLock.Sync#writerShouldBlock
:非公平模式下,直接返回 false, 先让当前线程获取锁.final boolean writerShouldBlock() { return false; // writers can always barge }
3.2 读锁尝试获取锁
-
java.util.concurrent.locks.ReentrantReadWriteLock.Sync#tryAcquireShared
:尝试获取读锁。它能够被多个线程同时持有,在没有其他写线程访问时,读锁总是获取成功。protected final int tryAcquireShared(int unused) { // 获取当前线程 Thread current = Thread.currentThread(); // 锁状态 int c = getState(); // 写锁不为0并且当前线程不是独占线程表示写锁被其他线程持有,直接返回-1 // 表示尝试获取读锁失败 if (exclusiveCount(c) != 0 && getExclusiveOwnerThread() != current) return -1; // 读锁数量 int r = sharedCount(c); // 走到这里,表示写锁为0,或者当前线程持有写锁 // 读锁是否需要等待(公平锁原则),持有线程小于最大数(65535) // CAS 设置读锁次数+1 if (!readerShouldBlock() && r < MAX_COUNT && compareAndSetState(c, c + SHARED_UNIT)) { // 读锁为0,表示首次获取读锁,设置当前线程为 firstReader,及其持有的读锁数量为1 if (r == 0) { firstReader = current; firstReaderHoldCount = 1; } else if (firstReader == current) { // 重入+1 firstReaderHoldCount++; } else { // 走到这里表示读锁被非当前线程获取 // 获取上一步的 cache HoldCounter rh = cachedHoldCounter; // 初始化 readHolds if (rh == null || rh.tid != getThreadId(current)) cachedHoldCounter = rh = readHolds.get(); else if (rh.count == 0) readHolds.set(rh); rh.count++; } // return 大于 0 的数,代表获取到了共享锁 return 1; } // 走到这里表示:readerShouldBlock 返回 true, // 1. 公平锁模式下,CLH 队列中存在节点排队; // 2. 非公平模式下,如果队列中 head 的第一个后继节点不是写锁,那么 CAS 抢夺读锁,不成功则返回 true。需要阻塞当前线程 return fullTryAcquireShared(current); }
-
java.util.concurrent.locks.ReentrantReadWriteLock.Sync#fullTryAcquireShared
:尝试获取共享锁.“读取锁的共享计数是否超过限制”等等进行处理。如果不需要阻塞等待,并且锁的共享计数没有超过限制,则通过CAS尝试获取锁,并返回1final int fullTryAcquireShared(Thread current) { HoldCounter rh = null; for (;;) { // 锁状态 int c = getState(); if (exclusiveCount(c) != 0) { // 写锁持有者不是当前线程 if (getExclusiveOwnerThread() != current) return -1; // else we hold the exclusive lock; blocking here // would cause deadlock. } else if (readerShouldBlock()) { // Make sure we're not acquiring read lock reentrantly // 进入这里表示写锁没有被占用,说明 CLH 队列存在其他线程占用 // 列头为当前线程 if (firstReader == current) { // assert firstReaderHoldCount > 0; } else { // 初始化 holdCounter 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; } } // 读锁超出最大范围 if (sharedCount(c) == MAX_COUNT) throw new Error("Maximum lock count exceeded"); // CAS设置读锁成功 if (compareAndSetState(c, c + SHARED_UNIT)) { // 如果是第1次获取“读取锁”,则更新firstReader和firstReaderHoldCount if (sharedCount(c) == 0) { firstReader = current; firstReaderHoldCount = 1; } else if (firstReader == current) { //如果想要获取锁的线程(current)是第1个获取锁(firstReader)的线程,则将firstReaderHoldCount+1 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 } // 返回大于 0 的数,代表获取到了读锁 return 1; } } }
4. 释放锁
4.1 写锁释放
java.util.concurrent.locks.AbstractQueuedSynchronizer#release
:释放锁。
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
-
java.util.concurrent.locks.ReentrantReadWriteLock.Sync#tryRelease
:尝试释放写锁,成功返回 true.protected final boolean tryRelease(int releases) { if (!isHeldExclusively()) throw new IllegalMonitorStateException(); int nextc = getState() - releases; boolean free = exclusiveCount(nextc) == 0; if (free) setExclusiveOwnerThread(null); setState(nextc); return free; }
4.2 读锁释放
-
java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock#unlock
:释放读锁public final boolean releaseShared(int arg) { // 公平、非公平模式下尝试释放锁一样 if (tryReleaseShared(arg)) { doReleaseShared(); return true; } return false; }
-
java.util.concurrent.locks.ReentrantReadWriteLock.Sync#tryReleaseShared
:尝试释放读锁。
protected final boolean tryReleaseShared(int unused) {
// 获取当前线程
Thread current = Thread.currentThread();
// 如果想要释放锁的线程为第一个获取锁的线程
if (firstReader == current) {
// assert firstReaderHoldCount > 0;
// 仅获取了一次,则需要将firstReader 设置null,否则 firstReaderHoldCount - 1
if (firstReaderHoldCount == 1)
firstReader = null;
else
firstReaderHoldCount--;
} else {
// 获取rh对象,并更新“当前线程获取锁的信息”
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
rh = readHolds.get();
int count = rh.count;
if (count <= 1) {
// 这一步将 ThreadLocal remove 掉,防止内存泄漏。因为已经不再持有读锁了
readHolds.remove();
if (count <= 0)
throw unmatchedUnlockException();
}
--rh.count;
}
// CAS更新同步状态
for (;;) {
int c = getState();
int nextc = c - SHARED_UNIT;
if (compareAndSetState(c, nextc))
// 如果 nextc == 0,那就是 state 全部 32 位都为 0,也就是读锁和写锁都空了
// 此时这里返回 true 的话,其实是帮助唤醒后继节点中的获取写锁的线程
return nextc == 0;
}
}
5 总结
- 获取写锁的时候,当前线程可以获取读锁,反之则不行,因为首先获取读锁后,在获取写锁,因为写锁获取的方法中,存在读锁,则获取失败需要阻塞。结果就会导致获取写锁的线程被休眠,可能后面没有被唤醒,导致死锁。
- 读锁可以被多线程获取,但写锁只能被一个线程获取。
- 存在线程获取写锁时,该线程可以继续获取读锁。这也是锁降级。