概述
ReentrantReadWriteLock 有2个锁,读锁和写锁。读读不加锁,读写,写读,写写都加锁。
读锁是共享锁,写锁是排他锁。 读锁和写锁的状态值被保存在int的值中,前16位表示 共享读锁状态值,后16位表示排他写锁状态值。
数据结构和核心参数
分析一下有关的属性
static final int SHARED_SHIFT = 16;//共享锁占据16位
static final int SHARED_UNIT = (1 << SHARED_SHIFT);//65536 1个共享锁状态值的单位,有1个共享锁就加上这个值。
static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1;//65535 写锁最大值
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;//65535 排他锁状态值掩码
/** Returns the number of shared holds represented in count */
static int sharedCount(int c) { return c >>> SHARED_SHIFT; }//获取共享锁状态值
/** Returns the number of exclusive holds represented in count */
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }//获取互斥锁状态值
类结构分析
ReentrantReadWriteLock依赖 WriteLock和ReadLock字段实现,ReadLock 和WriteLock依赖Sync字段实现,ReadLock,WriteLock,Sync都是ReentrantReadWriteLock的内部类,Sync继承了AQS。
源码分析
读锁加锁
public void lock() {
sync.acquireShared(1);
}
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)//子类实现
doAcquireShared(arg);//AQS 方法
}
ReentrantReadWriteLock.sync- tryAcquireShared
protected final int tryAcquireShared(int unused) {
Thread current = Thread.currentThread();
int c = getState();
if (exclusiveCount(c) != 0 &&//如果 写锁数不等于0 且写锁当前占用线程不等于当前线程,返回-1 ,获取读锁失败。(不等于当前线程,不能加锁)
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 != current.getId())
cachedHoldCounter = rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
}
return 1;//获取锁成功
}//readerShouldBlock()返回了true ,或加读单位失败 尝试获取锁
return fullTryAcquireShared(current);
}static final class NonfairSync extends Sync {
private static final long serialVersionUID = -8159625535654395037L;
final boolean writerShouldBlock() {
return false; // writers can always barge
}
final boolean readerShouldBlock() {
return apparentlyFirstQueuedIsExclusive();
}
}
//等待锁队列中有节点用,并且第一个节点是互斥模式
final boolean apparentlyFirstQueuedIsExclusive() {
Node h, s;
return (h = head) != null &&
(s = h.next) != null &&
!s.isShared() &&
s.thread != null;
}
/**
完整版本的读锁获取, 在tryAcquireShared中没有处理CAS 错过和重入读锁(线程相同 尝试加读锁)
*/
final int fullTryAcquireShared(Thread current) {
HoldCounter rh = null;
for (;;) {
int c = getState();//获取状态值
if (exclusiveCount(c) != 0) {//写锁数不等于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
if (firstReader == current) {//线程相同,要去获取锁
// assert firstReaderHoldCount > 0;
} else {
if (rh == null) {//获取并初始化锁记录
rh = cachedHoldCounter;
if (rh == null || rh.tid != current.getId()) {
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");
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 != current.getId())
rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
cachedHoldCounter = rh; // cache for release
}
return 1;//成功获取锁
}
}
}
获取读锁逻辑总结
- 如果写锁被其他线程持有,失败
- 尝试获取读锁,根据锁等待队列策略不需要读锁阻塞,尝试CAS修改读锁状态值获取锁。
- 完整版的获取读锁,处理CAS失败和锁重入,先检查写锁数,根据队列策略是否需要阻塞,然后 尝试CAS修改读锁状态值获取锁。
读锁释放
protected final boolean tryReleaseShared(int unused) {
Thread current = Thread.currentThread();
//清空当前线程记录,锁数量减1
if (firstReader == current) {
// assert firstReaderHoldCount > 0;
if (firstReaderHoldCount == 1)
firstReader = null;
else
firstReaderHoldCount--;
} else {
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != current.getId())
rh = readHolds.get();
int count = rh.count;
if (count <= 1) {
readHolds.remove();
if (count <= 0)
throw unmatchedUnlockException();
}
--rh.count;
}
for (;;) {//重试 直到成功减去一个单位的读锁。
int c = getState();
int nextc = c - SHARED_UNIT;
if (compareAndSetState(c, nextc))
// Releasing the read lock has no effect on readers,
// but it may allow waiting writers to proceed if
// both read and write locks are now free.
return nextc == 0;
}
}
逻辑总结:
清空当前线程记录,锁数量减1
重试 直到成功减去一个单位的读锁,如果修改后的读锁数为0返回true,释放锁。
写锁加锁
ReentrantReadWriteLock$NonfairSync(ReentrantReadWriteLock$Sync).tryAcquire(int)
protected final boolean tryAcquire(int acquires) {
思路概览:
- 如果读锁非0,或写锁非0且占用者非当前线程,失败
- 如果写锁数量饱和,失败
- 否则 如果是重入获取或队列策略允许,修改状态并保存 线程。成功
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) 如果 c != 0 and w == 0 这时 shared count != 0
if (w == 0 || current != getExclusiveOwnerThread())如果 读锁数为0,或写锁不为0且写锁的线程不是当前线程 获取写锁失败
return false;
if (w + exclusiveCount(acquires) > MAX_COUNT)//校验
throw new Error("Maximum lock count exceeded");
// Reentrant acquire
setState(c + acquires);//写锁数为0或写锁的线程是当前线程。修改状态值,获取写锁成功
return true;
}
if (writerShouldBlock() || 判断是否应该阻塞 默认的非公平锁返回false
!compareAndSetState(c, c + acquires)//尝试加写锁
return false;
setExclusiveOwnerThread(current);//加写锁成功,记录线程。
return true;
}
writerShouldBlock()
非公平锁模式返回false,不根据队列排队,直接争抢。
公平锁 检查队列中是否有合格的节点,有则返回false.
static final class FairSync extends Sync {
private static final long serialVersionUID = -2274990926593161451L;
final boolean writerShouldBlock() {
return hasQueuedPredecessors();
}
final boolean readerShouldBlock() {
return hasQueuedPredecessors();
}
}
队列中有节点要入队或 首节点的工作线程不是当前线程返回true
public final boolean hasQueuedPredecessors() {
// The correctness of this depends on head being initialized
// before tail and on head.next being accurate if the current
// thread is first in queue.
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());
//(s = h.next) == null 表示刚初始化完成队列头尾,即将入队。
}
获取写锁逻辑分析:
- 如果读锁非0,或写锁非0且占用者非当前线程,失败
- 如果写锁数量饱和,失败
- 否则 如果是重入获取或队列策略允许,修改状态并保存 线程。成功
写锁释放
ReentrantReadWriteLock$NonfairSync(AbstractQueuedSynchronizer).release(int)
//CAS互斥锁释放模板,重点关注tryRelease由写锁类实现
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
ReentrantReadWriteLock$NonfairSync(ReentrantReadWriteLock$Sync).tryRelease(int)
protected final boolean tryRelease(int releases) {
if (!isHeldExclusively())//判断当前线程是否持有锁
throw new IllegalMonitorStateException();
int nextc = getState() - releases;//计算 要修改的状态值
boolean free = exclusiveCount(nextc) == 0;
if (free)//如果写锁数等于0,销毁线程记录
setExclusiveOwnerThread(null);
setState(nextc);//修改状态值
return free;//根据 写锁数是否等于0 返回true或false.
}
逻辑分析:
- 异常判断,如果要释放的写锁不占用当前线程,抛出异常
- 计算并修改状态值
- 如果写锁数等于0,销毁线程记录
- 根据 写锁数是否等于0 返回true或false.
重入锁规则:获取读锁和写锁 在有写锁的情况下,相同线程也可以获取锁。
总结:ReentrantReadWriteLock依赖 WriteLock和ReadLock字段实现,WriteLock和ReadLock依赖ReentrantReadWriteLock.Sync实现, ReentrantReadWriteLock.Sync继承了 AQS。读锁和写锁公用一个int类型的状态值,前16位表示共享锁即读锁,后16位表示互斥锁即写锁。
读写锁的获取有相似步骤
- 如果存在互斥的锁,并且锁占用的线程不是当前线程,获取失败。
- 根据队列策略和是否是重入锁 尝试CAS加锁。