一、ReentrantReadWriteLock类结构介绍
ReentrantReadWriteLock类是ReadWriteLock接口的一个实现
ReadWriteLock接口值定义了两个方法返回值类型都是Lock
public interface ReadWriteLock {
Lock readLock(); // 获取读锁
Lock writeLock(); // 获取写锁
}
ReentrantReadWriteLock类中包含了1个继承了AQS的抽象内部类和Sync抽象类的两个实现类NonfairSync非公平同步器和FairSync公平同步器分别实现非公平锁和公平锁
以及两个Lock接口的实现类ReadLock读锁和WriteLock写锁
当我们new一个ReentrantReadWriteLock的时候做了什么事情呢?
ReentrantReadWriteLock类有两个构造函数无参和带一个布尔值的构造函数
public ReentrantReadWriteLock() {
this(false);
}
public ReentrantReadWriteLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
readerLock = new ReadLock(this);
writerLock = new WriteLock(this);
}
通过源码我们看到当new一个ReentrantReadWriteLock的时候会先初始化同步器,然后再初始化读锁和写锁,在初始化读锁和写锁的时候会将之前初始化好的同步器sync分别同步给读锁和写锁
当在构造函数中传入true的时候则表示使用的是公平锁反之使用非公平锁,默认使用的是公平锁。
进一步观察ReadLock和WriteLock我们发现ReadLock采用的是共享锁,而WriteLock采用的是独占锁
看到这里我们似乎对读写锁的结构有了一个大致的了解了。那么读写锁中写锁的获取与释放的原理过程是什么样的呢?读锁的获取与释放又是什么样的呢?
在之前的文章中我说过读写锁同一时刻只能有一种类型的线程在工作,要么是读线程要么是写线程,如果是写线程同一时刻只能存在一个写线程,如果是读线程同一时刻允许有多个读线程。这又是怎么实现的呢?
同时看到ReentrantReadWriteLock类的大致结构发现ReentrantReadWriteLock类中的同步器只有一个同步状态变量state,我们的Doug Lee大师又是怎么通过这1个state变量来控制两种锁的呢?
带着这么多的疑问我们一起来看源码,来一一回答我们心中的疑惑吧
二、ReentrantReadWriteLock读写锁中WriteLock写锁的获取和释放
1、WriteLock类的lock()方法源码解析
public void lock() {
sync.acquire(1);
}
sync.acquire(1)->AbstractQueuedSynchronizer#acquire(int arg)
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
在这个acquire方法中会先尝试获取独占锁,tryAcquire(arg)方法会调用到ReentrantReadWriteLock类中的Sync类的tryAcquire方法
protected final boolean tryAcquire(int acquires) {
// 获取当前线程对象
Thread current = Thread.currentThread();
// 获取当前锁对象的同步状态
int c = getState();
// 将同步状态的值与((1 << 16) - 1)做 & 运算,得出state同步状态值所对应的独占锁的数量
int w = exclusiveCount(c);
if (c != 0) {
// 如果 c != 0 并且 w == 0的话,则说明当前的锁是共享锁(读锁),正被读线程所占用 或者 当前独占锁不是被当前线程所占用的
if (w == 0 || current != getExclusiveOwnerThread())
return false;
// 已有的独占锁的数量+acquires 不能大于 同步状态的最大值65535
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// Reentrant acquire
// 程序走到这边说明是当前线程写锁重入 加上acquires即可
setState(c + acquires);
return true;
}
// 说明当前锁对象还没有被任何线程独占
// 根据初始化ReentrantReadWriteLock的时候是使用的公平锁还是非公平锁来决定是调用FairSync还是NonfairSync,如果是非公平锁的话则直接返回false,再以CAS的形式设置同步状态的值
// 非公平锁的话则判断同步队列中是否有待获取锁的节点,如果有节点且该节点对应的线程不是当前线程的话则尝试获取独占锁失败
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))
return false;
// 代码走到这里说明当前线程获取到独占锁了,将当前线程与锁进行绑定
setExclusiveOwnerThread(current);
return true;
}
写线程的尝试获取独占锁的过程根据上面的代码和注释我们已经很清楚了,在这里再对这段逻辑进行小结下(这里面涉及到的位运算还是比较简单的,这里就不做过多的描述了,需要注意的是 Doug Lee大师将int类型的state转为32位的2进制前16位表示读锁 后16位表示写锁)
获取独占锁失败的因素主要包含:
(1)、当前锁对象被读线程占有
(2)、当前锁对象被另外一个写线程占有
(3)、当前同步状态的值+预加值超过了最大的同步状态值(这种情况会直接抛出Error异常)
(4)、如果是非公平锁在进行CAS形式的设置同步状态的值的时候没有成功
(5)、如果是公平锁的话同步队列中首节点与尾节点不相等且首节点的下一个节点对应的线程不等于当前线程(即首节点下的第一个节点封装的不是当前线程),或者同步队列中没有等待的线程但是在以CAS的形式设置值的时候失败
综上如果尝试获取独占锁失败则会将当前线程封装成Node对象加入到等待队列中去,具体的过程可以参照我的上一篇博客https://www.jianshu.com/p/955a3d0d5fb6
2、WriteLock类的unLock()方法源码解析
public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
// 在这边会进行尝试释放锁
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
tryRelease(arg)尝试释放独占锁方法的逻辑
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;
}
需要注意的是只有当当前线程将同步状态变成0的时候才是真正的将锁释放掉,释放完成之后,它会调用LockSupport的unpark(Thread thread)静态方法获取等待队列中的下一个节点。
三、ReentrantReadWriteLock读写锁中ReadLock读锁的获取和释放
1、ReadLock类的lock()方法源码解析
public void lock() {
// 尝试获取共享锁
sync.acquireShared(1);
}
public final void acquireShared(int arg) {
// 首先尝试获取共享锁
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
protected final int tryAcquireShared(int unused) {
// 获取当前线程对象
Thread current = Thread.currentThread();
// 获取同步状态的值
int c = getState();
// 将同步状态的值与 ((1 << 16) - 1)进行&运算 如果结果!=0并且占用锁的线程不是当前线程直接返回-1
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
// 获取共享锁对应的state的值 实则取得是 state值的前16位并将其转为int类型
int r = sharedCount(c);
// 这个判断的意思主要是:
// 1、如果是公平锁则判断等待队列等是否存在正在等待的节点且首节点的next节点锁对应的线程 != 当前线程的话返回true 就不会执行if里面的语句
// 2、如果是非公平锁需要确保等待队列中如果正在等待的节点,且第一个节点是对应的是写线程也不会执行if语句里面的内容
if (!readerShouldBlock() &&
r < MAX_COUNT &&
compareAndSetState(c, c + SHARED_UNIT)) {
// if语句里面的内容主要是用来设置当前线程所对应的获取到的共享锁的次数的
if (r == 0) {
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {
firstReaderHoldCount++;
} else {
// HoldCounter类中包含了两个成员变量一个是线程id 一个是线程对应的共享锁数量 同时这对象存在了ThreadLocal中
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;
}
// 如果上面的操作没有成功的话就会以自旋CAS的形式获取共享锁
return fullTryAcquireShared(current);
}
2、ReadLock类的unLock()方法源码解析
public void unlock() {
sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
// 尝试释放共享锁
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
protected final boolean tryReleaseShared(int unused) {
Thread current = Thread.currentThread();
if (firstReader == current) {
if (firstReaderHoldCount == 1)
firstReader = null;
else
firstReaderHoldCount--;
} else {
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
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;
}
}
释放共享锁的这个过程与释放独占锁比较类似,这里就不做过多的描述了