ReentrantReadWriteLock 是 ReadWriteLock 接口的具体实现。和 ReentrantLock 一样,它使用 Sync (继承自 AQS 抽象类)作为锁的同步器,支持公平同步器和非公平同步器,分别在 FairSync 和 NonfairSync 中实现。
在 AQS 同步器的基础上,此 Lock 实现了两种类型的锁,并把它们作为内部属性。这两种锁分别是读锁(共享锁) ReadLock 和 写锁(独占锁,排他锁)WriteLock。
ReentrantReadWriteLock有五个内部类,五个内部类之间也是相互关联的。内部类的关系如下图所示:
如上图所示,Sync继承自AQS、NonfairSync继承自Sync类、FairSync继承自Sync类;ReadLock实现了Lock接口、ReadLock也实现了Lock接口。
具体他们如何联系,接下来看源码分析就知道了。
和ReentrantLock中的Sync类一样,该类继承AbstractQueuedSynchronizer类,所以Sync是基于AQS实现的一个同步器,我们知道AQS中已经指定了一套完整的管理同步队列和等待队列的方案,以及定义了不同的获取方式,默认支持了独占模式和共享模式。而Sync要做的就是定义同步状态的含义,以及决定什么同步状态下,资源可以访问到。我们可以在分析源码过程中思考这个问题。
在ReentrantReadWriteLock中,对同步状态有如下规定:
state原本是32位int类型整数,将其划分为高16位和低16位。
高16位代表读锁的同步器状态,记录了它一共被线程持有了多少个数,
低16位代表写锁的同步器状态,记录了它一共被线程持有了多少个数。
所以不管是读锁还是写锁,他们的最大锁数是2的16次方-1。
SHARED_UNIT表示读锁状态加1是,state需要加的一个单位数,它是2的16次方。
EXCLUSIVE_MASK 表示的是写锁的掩码,它是2的16次方-1,即它的低16位都是1。高位都是0.
/*
* Read vs write count extraction constants and functions.
* Lock state is logically divided into two unsigned shorts:
* The lower one representing the exclusive (writer) lock hold count,
* and the upper the shared (reader) hold count.
*/
// 最多支持 65535(1<<16 - 1) 个写锁和 65535 个读锁
// int 值的低十六位表示写锁计数,高十六位表示持有读锁的线程数
static final int SHARED_SHIFT = 16;
// 增加一个线程获取读锁,则持有数加 SHARED_UNIT,因为只有高十六位
// 才表示读锁数量
static final int SHARED_UNIT = (1 << SHARED_SHIFT);
// 锁的最大数量
static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1;
// 写锁计数掩码(低十六位的二进制全部为 1)
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
/** Returns the number of shared holds represented in count */
// 返回当前持有读锁的线程数(高十六位表示读锁计数,所以无符号右移 16 位)
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; }
sharedCount获取读锁的状态,>>>表示无符号右移,高位补0而不是符号位。
exclusiveCount获取写锁的状态,通过和掩码取&,即获得低16位的数。
HoldCounter是对线程id以及对应线程持有读锁个数的一个包装。
ThreadLocalHoldCounter继承ThreadLocal < HoldCounter >,并且重写了initialValue方法,使得第一次get的初始值不再是null,而是空的HoldCounter实例。
对于该类的使用可以参考上一篇文章JUC ThreadLocal原理,通过源码进行学习深入了解
/**
* 持有读锁的线程计数器。记录单个线程持有的读锁数量。
*/
//线程持有几个读锁的计时器类,
//2个成员函数,int类型的读锁个数,long类型的线程ID。
static final class HoldCounter {
int count = 0;
// Use id, not reference, to avoid garbage retention
// 当前线程的 id
final long tid = getThreadId(Thread.currentThread());
}
/**
* ThreadLocal 的子类。
*/
//继承ThreadLocal的子类,
static final class ThreadLocalHoldCounter
extends ThreadLocal<HoldCounter> {
//重写了initialValue方法,则初始化thread中的Map中的key时,不为null,而是HoldCounter的默认实例
public HoldCounter initialValue() {
return new HoldCounter();
}
}
这些变量主要是关于线程与线程持有读锁数的一个关系记录。
readHolds继承ThreadLocals,所以功能主要是让线程持有自己的一份HoldCounter 变量。
cachedHoldCounter是为了节省 readHolds的查找。
firstReader 和firstReaderHoldCount是为了实现写锁降级读锁时会用到的变量。
构造函数Sync()中对readHolds进行了初始化赋值。并使得它在所有线程中可见。
/**
* 当前线程持有的可重入读锁的数量。
* 仅在构造函数和 readObject 中初始化。
* 当读线程的持有计数下降到 0 时删除。
*/
private transient ThreadLocalHoldCounter readHolds;
/**
* 成功获取 readLock 的最后一个线程的持有计数。此变量在通常情况下
* 节省了 ThreadLocal 的查找,因为下一个要 release 的线程是最后一个
* acquire 的。这是 non-volatile 的,只作为一种启发使用,而且对于线程
* 缓存来说非常好。
*
* 可以比存储读持有计数的线程活的更久,但是通过不保留线程的引用来
* 避免垃圾保留。
*
* 通过良性呃数据竞争访问;依赖于内存模型的 final 字段和非空保证。
*/
private transient HoldCounter cachedHoldCounter;
/**
* firstReader 是获得读锁的第一个线程。
* firstReaderHoldCount 是 firstReader 的持有计数。
*
* 更准确地说,firstReader 是最后一次将共享计数从 0 更改为 1 的唯一
* 线程,并且从那时起就没有释放读锁;如果没有这样的线程则为 null。
*/
private transient Thread firstReader = null;
private transient int firstReaderHoldCount;
// 构造函数
Sync() {
readHolds = new ThreadLocalHoldCounter();
setState(getState()); // 确保readHolds的可见性。ensures visibility of readHolds
}
/**
* 如果当前线程在尝试获取读锁时应该阻塞,则返回 true。
*/
abstract boolean readerShouldBlock();
/**
* 如果当前线程在尝试获取读锁时应该阻塞,则返回 true。
*/
abstract boolean writerShouldBlock();
为了能明白公平锁和非公平锁在tryAcquire 和tryAcquireShared中的差别,这里先将子类NonfairSync和FairSync中的实现进行分析:
/**
* 同步器的非公平版本
* 实现了 writerShouldBlock 和 readerShouldBlock
*/
static final class NonfairSync extends Sync {
private static final long serialVersionUID = -8159625535654395037L;
final boolean writerShouldBlock() {
return false; // writers can always barge
}
final boolean readerShouldBlock() {
/* As a heuristic to avoid indefinite writer starvation,
* block if the thread that momentarily appears to be head
* of queue, if one exists, is a waiting writer. This is
* only a probabilistic effect since a new reader will not
* block if there is a waiting writer behind other enabled
* readers that have not yet drained from the queue.
*/
return apparentlyFirstQueuedIsExclusive();
}
}
//是否队列的第一个等待获取的是以独占模式在请求资源
final boolean apparentlyFirstQueuedIsExclusive() {
Node h, s;
return (h = head) != null && //头节点不为空
(s = h.next) != null && //头节点的next也不为空
!s.isShared() && //如果head.next不是共享节点,
s.thread != null; //且这个节点的thread是有效的。那么返回true
}
/**
* 同步器的非公平版本
* 实现了 writerShouldBlock 和 readerShouldBlock(有后继者则必须等待)
*/
static final class FairSync extends Sync {
private static final long serialVersionUID = -2274990926593161451L;
final boolean writerShouldBlock() {
return hasQueuedPredecessors();
}
final boolean readerShouldBlock() {
return hasQueuedPredecessors();
}
}
(1)tryAcquire
// 获取独占锁,基本和 ReentrantLock 一样
protected final boolean tryAcquire(int acquires) {
/*
* Walkthrough:
* 1. If read count nonzero or write count nonzero
* and owner is a different thread, fail.
* 2. If count would saturate, fail. (This can only
* happen if count is already nonzero.)
* 3. Otherwise, this thread is eligible for lock if
* it is either a reentrant acquire or
* queue policy allows it. If so, update state
* and set owner.
*/
Thread current = Thread.currentThread();
int c = getState(); //得到同步器的state
int w = exclusiveCount(c); //得到state中记录写锁的计数值,它在state中的后低6位中
// 写锁或读锁已经被获取,只有当前线程独占了写锁,即重入才可以。
//@注意:如果已经先获取了写锁,不管之后该线程有没有获得读锁,再次重入写锁也是可以的。
if (c != 0) {
// (Note: if c != 0 and w == 0 then shared count != 0)
// 写锁为 0 (即读锁已被获取)或者得到锁的不是当前线程,则获取失败
// 说明:其他线程持有读锁时,当前线程写锁不能获取到资源
//当前线程持有读锁时,如果线程未持有独占锁,即独占锁的持有者不是当前线程,也不能获取到资源。
//即读锁不能升级成写锁。
if (w == 0 || current != getExclusiveOwnerThread())
return false;
// 写锁重入持有计数已达到限制,则获取失败,最大为2的16次方-1
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// Reentrant acquire
// 重入获取成功,当且仅当,持有w!=0&¤t==getExclusiveOwnerThread()
setState(c + acquires);
return true;
}
//到这一步说明state为0,则读锁写锁都没有线程拥有。
// 当前线程应该阻塞,或者 CAS 设置状态失败,则获取锁失败
// 1.如果是公平锁,那么writerShouldBlock只允许队列头的线程获取锁
// 2.如果是非公平锁,不做限制,writerShouldBlock直接返回false
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))
return false;
// 否则获取成功,设置独占写锁。
setExclusiveOwnerThread(current);
return true;
}
(2)tryRelease
/*
* Note that tryRelease and tryAcquire can be called by
* Conditions. So it is possible that their arguments contain
* both read and write holds that are all released during a
* condition wait and re-established in tryAcquire.
*/
// 释放独占锁,基本和 ReentrantLock 一样
protected final boolean tryRelease(int releases) {
// 当前线程不是持有独占锁的线程,抛出异常
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
// 持有计数减少
int nextc = getState() - releases;
//独占锁的state状态是否为0,为0 才是true,不为0 为false
boolean free = exclusiveCount(nextc) == 0;
// 只有持有计数为 0 的时候才会完全释放
if (free)
setExclusiveOwnerThread(null); //当独占资源状态为0时,释放当前线程锁定
setState(nextc); //更新state。
return free; //成功释放返回true。失败返回false
}
(1)tryAcquireShared
// 共享模式获取锁(获取读锁)
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();
int c = getState();
// exclusiveCount(c) != 0 独占计数不等于 0,说明有线程持有写锁
// 持有写锁的线程如果不是当前线程,则当前线程不能获取读锁。
//返回-1。负数表示获取失败
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
//如果没有返回,说明要么写锁没有被其他线程拥有,要么写锁被当前线程拥有
//获取写锁后,可以再获取读锁,这时锁降级的原理。
// 获取持有读锁的同步器状态值,
int r = sharedCount(c);//r记录进行CAS前的读锁的状态值
// 1.readerShouldBlock(),在非公平锁中:如果exclusiveCount(c)不为0,就有可能导致同步队列中
//存在竞争写锁而阻塞的线程,如果head.next是个独占模式节点,则发返回true。
//在公平锁中:如果同步队列中有正在等待的线程,不管是acquire读锁还是写锁,都会返回true。
// 2.没有达到最大数量
// 3.满足前面条件就进行CAS 更新,将c变为c+SHARED_UNIT。
//成功将进行firstReader、firstReaderHoldCount、cachedHoldCounter的状态更新。
if (!readerShouldBlock() &&
r < MAX_COUNT &&
compareAndSetState(c, c + SHARED_UNIT)) {
// 如果未CAS之前的读锁状态为0,说明当前线程是第一个获得读锁的线程
//第一次获得读锁的线程,没有调用readHolds.get,说明线程map中没有存储该映射。
if (r == 0) {
firstReader = current;// 初始化 firstReader 和 firstReaderHoldCount
firstReaderHoldCount = 1;
} else if (firstReader == current) {//读锁不是第一次被获取。
// 但是当前线程就是首次获取的线程,即读锁重入,
// 计数加 1
firstReaderHoldCount++;
} else {//不是第一次获取读锁,且当前线程也不是firstReader。则写锁状态必定为0,
//cachedHoldCounter为记录的最后一次获得读锁的线程。
HoldCounter rh = cachedHoldCounter;
//如果最后一次获取读锁的线程为空,或者不是当前线程,则更新cachedHoldCounter。
if (rh == null || rh.tid != getThreadId(current))
//readHolds的get会以当前线程为key,到当前线程的threadLocals映射中找到记录的holdCount
//如果线程之前没有拥有过该读锁,那么它会新建一个ThreadLocal.ThreadLocalMap,
//并且以key=当前线程,value=new HoldCounter()的entry放入Map中,
//这个Map最后会赋值给当前线程的threadLocals。
cachedHoldCounter = rh = readHolds.get();
else if (rh.count == 0) //如果当前线程是最后一次获得读锁的线程,但记数为0。
readHolds.set(rh); //在count为1时,释放过读锁,则一定调用过readHold.remove,所以要重新set。
//不等于null,并且当前线程是cachedHoldCounter缓存中的最后一个线程,
//则直接从变量rh中拿,不从map中获取,性能快那么一点点。
rh.count++;
}
// 返回 1 表示获取成功
return 1;
}
// 一次获取读锁失败后,尝试循环获取
//失败原因可能有,CAS过程中失败,公平锁中,同步队列中有节点等待。
//非公平锁中,同步队列中head.next是一个独占模式节点在等待
//或者锁超过了2的16次方-1
return fullTryAcquireShared(current);
}
接着分析fullTryAcquireShared
/**
* 读锁 acquire 的完整版本,包含 CAS 失败或者 tryAcquireShared 中
* 没有获取可重入读锁的情况
* tryAcquireShared 是一次快速获取的情况,且里面用到的 CAS 只允许一个
* 线程获取成功,但读锁是共享的,所以需要此函数来循环获取,直到成功。
*/
//该方法处理CAS失败,或者重入失败的情况。
final int fullTryAcquireShared(Thread current) {
/*
* 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 (;;) {
int c = getState();
// 如果有线程获取到了写锁
if (exclusiveCount(c) != 0) {
// 判断获取到写锁的是不是当前线程,如果不是则返回获取失败
//但是如果是当前线程持有写锁,那么可以尝试获取多次读锁,这个在低下会进行处理。
if (getExclusiveOwnerThread() != current)
return -1;
}
//没有线程获得了写锁,但是因为公平锁队列存在等待节点,
//或者非公平锁中,下一个是独占模式节点,即争取写锁。
else if (readerShouldBlock()) {
// Make sure we're not acquiring read lock reentrantly
// 如果当前线程是 firstReader,一定是重入,
//因为如果firstReaderHoldCount为0,firstReader应该为null。
if (firstReader == current) {
//重入在下面代码判断。
// assert firstReaderHoldCount > 0;
} else { //如果不是第一个获取读锁的线程,有可能在重入,也有可能是首次获取读锁
if (rh == null) {
rh = cachedHoldCounter;
// 如果当前线程不是缓存的最后一个线程,从 readHolds 里读取
if (rh == null || rh.tid != getThreadId(current)) {
rh = readHolds.get();
if (rh.count == 0) //如果发现当前线程是首次获取锁,那么它确实应该被阻塞。
readHolds.remove(); //将get创建的映射删除掉,反正内存泄漏,
}
}
// 注意:
// 已经获取了读锁的线程重入时,不能阻塞,阻塞会导致死锁。
// 所以如果得到count为0,说明该线程不是重入,那么它就应该被阻塞。
if (rh.count == 0)
return -1; //失败返回-1.
}
}
//到这一步,只可能是重入,或者锁降级的情况、或者是锁过多了。
// 读锁的数量达到限制
if (sharedCount(c) == MAX_COUNT) //先排除锁过多情况
throw new Error("Maximum lock count exceeded");
// 此线程不应该被阻塞,且读锁的数量没有达到限制,那么可以获取读锁
// 获取读锁成功,下面的处理和 tryAcquireShared 类似
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; // 更新cachedHoldCounter
}
return 1;
}
}
}
(2)tryReleaseShared
// 共享模式释放锁
protected final boolean tryReleaseShared(int unused) {
Thread current = Thread.currentThread();
// 如果当前为第一个获取读锁的线程,即最后一个使读锁的state从0变为1的线程
if (firstReader == current) {
// assert firstReaderHoldCount > 0;
// 如果只持有一次,则可以释放,将 firstReader 设为空,否则计数
// 减 1
if (firstReaderHoldCount == 1) //只持有1次,就释放 并重置firstReader
firstReader = null;
else //不止1次,则次数减1
firstReaderHoldCount--;
} else { //如果当前线程不是第一个获得读锁的
// 获取当前线程的计数器
HoldCounter rh = cachedHoldCounter;
//如果当前线程不是最后一个获取所得线程,即无法从cachedHoldCounter中直接获取
if (rh == null || rh.tid != getThreadId(current))
rh = readHolds.get(); //则获得当前线程map中存储的HoldCounter
// 得到当前线程持有读锁的次数count。
int count = rh.count;
// 持有数小于等于1,
if (count <= 1) {
//注意以下remove只是将线程的map中的底层table的entry引用删除了,如果cachedHoldCounter此时引用entry,它的值不会为null。
readHolds.remove();//不管是count为1,还是为0,将threadLocals中的映射直接删除,避免内存泄漏,
if (count <= 0) //如果小于等于0,说明没有lock就进行unlock,抛出异常。
throw unmatchedUnlockException();
}
// 线程持有锁数减1
--rh.count;
}
// 更新完持有计数之后,自旋更新同步器状态,把读锁的数量减 1
//自旋的原因是因为,同一时间有可能有多个读锁正在进行释放锁
for (;;) {
int c = getState();
int nextc = c - SHARED_UNIT; //释放资源,同步器状态需要减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; //说明只有读锁完全释放,才会唤醒后继线程,写锁才有机会争取到。
}
}
核心方法都是使用Sync,可参考ReentrantLock如何调用Sync中方法。
这边只讲它们怎么联系起来
public static class ReadLock implements Lock, java.io.Serializable {
private static final long serialVersionUID = -5992448646407690164L;
private final Sync sync; //读锁类的内部同步器
/**
* 构造函数
*
* @param lock the outer lock object
* @throws NullPointerException if the lock is null
*/
protected ReadLock(ReentrantReadWriteLock lock) {
sync = lock.sync; //在构造函数中同步器会使用读写锁中同步器
}
***以下代码省略
ReadLock构造函数中,会将自己锁的同步器,赋值为ReentrantReadWriteLock中的Sync.
/**
* 写锁的实现
*/
public static class WriteLock implements Lock, java.io.Serializable {
private static final long serialVersionUID = -4992448646407690164L;
private final Sync sync;
/**
* 构造函数
*
* @param lock the outer lock object
* @throws NullPointerException if the lock is null
*/
protected WriteLock(ReentrantReadWriteLock lock) {
sync = lock.sync;
}
***以下代码省略
WriteLock构造函数中,会将自己锁的同步器,赋值为ReentrantReadWriteLock中的Sync.
public class ReentrantReadWriteLock
implements ReadWriteLock, java.io.Serializable {
// 版本序列号
private static final long serialVersionUID = -6992448646407690164L;
// 读锁
private final ReentrantReadWriteLock.ReadLock readerLock;
// 写锁
private final ReentrantReadWriteLock.WriteLock writerLock;
// 同步队列
final Sync sync;
private static final sun.misc.Unsafe UNSAFE;
// 线程ID的偏移地址
private static final long TID_OFFSET;
static {
try {
UNSAFE = sun.misc.Unsafe.getUnsafe();
Class<?> tk = Thread.class;
// 获取线程的tid字段的内存地址
TID_OFFSET = UNSAFE.objectFieldOffset
(tk.getDeclaredField("tid"));
} catch (Exception e) {
throw new Error(e);
}
}
}
说明: 可以看到ReentrantReadWriteLock属性包括了
/**
* ReentrantReadWriteLock 构造函数,默认使用非公平排序属性。
*/
public ReentrantReadWriteLock() {
this(false);
}
/**
* 使用给定的公平性策略创造一个新的 ReentrantReadWriteLock。
*
* @param fair {@code true} if this lock should use a fair ordering policy
*/
public ReentrantReadWriteLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
readerLock = new ReadLock(this);
writerLock = new WriteLock(this);
}
//提供给ReentrantReadWriteLock实例的获取写锁和读锁的方法
public WriteLock writeLock() { return writerLock; }
public ReadLock readLock() { return readerLock; }
说明:
对ReentrantReadWriteLock的操作基本上都转化为了对Sync对象的操作,而Sync的函数已经分析过,不再累赘。
同一个线程中,在没有释放读锁的情况下,就申请写锁,叫锁升级,ReentrantReadWriteLock 不允许锁升级。
同一个线程中,在没有释放写锁的情况下,就申请读锁,叫锁降级,ReentrantReadWriteLock 支持锁降级。
RentrantReadWriteLock不支持锁升级(把持读锁、获取写锁,最后释放读锁的过程)。目的也是保证数据可见性,如果读锁已被多个线程获取,其中任意线程成功获取了写锁并更新了数据,则其更新对其他获取到读锁的线程是不可见的。
锁降级中读锁的获取是否必要呢? 答案是必要的。主要是为了保证数据的可见性,如果当前线程不获取读锁而是直接释放写锁,假设此刻另一个线程(记作线程T)获取了写锁并修改了数据,那么此时才获取读锁去读数据,是读不到原始数据的。而如果遵循锁降级的步骤,则线程T将会被阻塞,直到当前线程使用数据并释放读锁之后,线程T才能获取写锁进行数据更新。
JUC锁: ReentrantReadWriteLock详解
J.U.C之locks框架:基于AQS的读写锁(5)