前言
前面我们分析了Synchronized(同步锁),ReentrantLock(独占锁),本篇开始分析ReentrantReadWriteLock(读是共享锁,写是独占锁)。
1、ReentrantReadWriteLock结构图
2、调用的方法关系图
3、获取共享锁
- ReadLock中的lock方法,源码如下:
public void lock() {
//Sync继承AQS,此方法实现在AQS中
sync.acquireShared(1);
}
- AQS中的acquireShared方法,源码如下
public final void acquireShared(int arg) {
//先尝试获取锁,获取成功则返回,失败则执行doAcquireShared方法再去获取锁
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
- tryAcquireShared()定义在ReentrantReadWriteLock.java的Sync中,源码如下:
protected final int tryAcquireShared(int unused) {
Thread current = Thread.currentThread();
// 在读写锁模式下,高16位存储的是共享锁(读锁)被获取的次数,低16位存储的是互斥锁(写锁)被获取的次数
int c = getState();
//如果独占锁(写锁)已经被获取并且获取独占锁的线程不是当前线程的话,则返回-1
//如果是独占锁是当前线程获取,则当前线程也可以获取读锁,锁降级
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
//获取“读取锁”的共享计数
int r = sharedCount(c);
// 如果不需要阻塞等待,并且“读取锁”的共享计数小于MAX_COUNT;
// 则通过CAS函数更新“读取锁”的共享计数+1
if (!readerShouldBlock() &&
r < MAX_COUNT &&
compareAndSetState(c, c + SHARED_UNIT)) {
//第一次获取读取锁
if (r == 0) {
firstReader = current;
firstReaderHoldCount = 1;
//如果当前线程是第1个获取锁的线程
} else if (firstReader == current) {
firstReaderHoldCount++;
} else {
// HoldCounter统计的是当前线程获取“读取锁”的次数
//下面这几行,就是将 cachedHoldCounter 设置为当前线程
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
// cachedHoldCounter 是否缓存的是当前线程,不是的话要到 ThreadLocal 中取
cachedHoldCounter = rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
}
return 1;
}
//if条件失败,则进入这个方法
return fullTryAcquireShared(current);
}
- fullTryAcquireShared()在ReentrantReadWriteLock.java的Sync中,源码如下:
final int fullTryAcquireShared(Thread current) {
HoldCounter rh = null;
for (;;) {
int c = getState();
// 如果“锁”被“写锁”持有,并且获取锁的线程不是current线程;则返回-1。
if (exclusiveCount(c) != 0) {
if (getExclusiveOwnerThread() != current)
return -1;
// 需要阻塞等待
} else if (readerShouldBlock()) {
//进入这里,说明写锁被释放,读锁被阻塞
//那么逻辑就很清楚了,这里是处理读锁重入的
if (firstReader == current) {
} else {
if (rh == null) {
rh = cachedHoldCounter;
if (rh == null || rh.tid != current.getId()) {
rh = readHolds.get();
if (rh.count == 0)
readHolds.remove();
}
}
// 如果当前线程获取锁的计数=0,则返回-1,去排队。
if (rh.count == 0)
return -1;
}
}
if (sharedCount(c) == MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// 将线程获取“读取锁”的次数+1。
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;
}
}
}
- doAcquireShared()定义在AQS函数中,源码如下:
private void doAcquireShared(int arg) {
// addWaiter(Node.SHARED)方法前面分析过,作用是:
//创建“当前线程”对应的节点,并将该线程添加到CLH队列中
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
// 获取当前节点上一个节点
final Node p = node.predecessor();
// 如果p是头节点,则尝试获取共享锁。
if (p == head) {
//此处是上面分析过的tryAcquireShared方法,这里可以看出:
//doAcquireShared方法其实就是在循环的调用tryAcquireShared来尝试获取共享锁
int r = tryAcquireShared(arg);
//如果成功
if (r >= 0) {
// 头节点后移并传播
// 传播即唤醒后面连续的读节点
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
//此处判断是否进行阻塞等待,若阻塞等待过程中,线程被中断过,则设置interrupted为true。
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
4、释放共享锁
- ReadLock中的unLock方法,源码如下:
public void unlock() {
//Sync继承AQS,此方法实现在AQS中
sync.releaseShared(1);
}
- releaseShared()在AQS中实现,源码如下:
public final boolean releaseShared(int arg) {
//这里的思路和获取锁一样:
//通过tryReleaseShared()去尝试释放共享锁,成功直接返回
//失败,则通过doReleaseShared释放共享锁
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
- tryReleaseShared()定义在ReentrantReadWriteLock.java的Sync中,源码如下:
protected final boolean tryReleaseShared(int unused) {
Thread current = Thread.currentThread();
// 如果当前线程是第1个获取锁的线程
if (firstReader == current) {
// assert firstReaderHoldCount > 0;
//如果等于 1,那么这次释放锁后就不再持有锁了,把 firstReader 置为 null,给后来的线程用
if (firstReaderHoldCount == 1)
firstReader = null;
else
firstReaderHoldCount--;
} else {
// 判断 cachedHoldCounter 是否缓存的是当前线程,不是的话要到 ThreadLocal 中取
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != current.getId())
rh = readHolds.get();
int count = rh.count;
if (count <= 1) {
// 这一步将 ThreadLocal remove 掉,防止内存泄漏。因为已经不再持有读锁了
readHolds.remove();
if (count <= 0)
//这里的情况是,lock() 一次,unlock() 好几次才会触发
throw unmatchedUnlockException();
}
//当前线程获取“读取锁”的次数-1
--rh.count;
}
for (;;) {
// 获取锁的状态
int c = getState();
// 将锁的获取次数-1。
int nextc = c - SHARED_UNIT;
// 通过CAS更新锁的状态。
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
- doReleaseShared()定义在AQS中,源码如下:
private void doReleaseShared() {
for (;;) {
// 获取CLH队列的头节点
Node h = head;
// 如果头节点不为null,并且头节点不是队列最后一个节点
if (h != null && h != tail) {
// 获取头节点对应的线程的状态
int ws = h.waitStatus;
// 判断头节点是否是SIGNAL状态
//如果是,下一个节点所对应的线程需要被唤醒。
if (ws == Node.SIGNAL) {
// 设置“头节点对应的线程状态”为0。失败的话,则继续循环。
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue;
// 唤醒“头节点的下一个节点所对应的线程”。
unparkSuccessor(h);
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
// 如果头节点发生变化,则继续循环。否则,退出循环。
if (h == head) // loop if head changed
break;
}
}
5、实例
class CachedData {
Object data;
volatile boolean cacheValid;
// 读写锁实例
final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
void processCachedData() {
// 获取读锁
rwl.readLock().lock();
if (!cacheValid) { // 如果缓存过期了,或者为 null
// 释放掉读锁,然后获取写锁 (后面会看到,没释放掉读锁就获取写锁,会发生死锁情况)
rwl.readLock().unlock();
rwl.writeLock().lock();
try {
if (!cacheValid) { // 重新判断,因为在等待写锁的过程中,可能前面有其他写线程执行过了
data = ...
cacheValid = true;
}
// 获取读锁 (持有写锁的情况下,是允许获取读锁的,称为 “锁降级”,反之不行。)
rwl.readLock().lock();
} finally {
// 释放写锁,此时还剩一个读锁
rwl.writeLock().unlock(); // Unlock write, still hold read
}
}
try {
use(data);
} finally {
// 释放读锁
rwl.readLock().unlock();
}
}
}
结束语
本篇到此ReentrantReadWriteLock的共享锁获取和释放的源码就分析完了,下一篇将对信号量Semaphore进行分析。
如果你觉得本篇文章对你有帮助的话,请帮忙点个赞,再加一个关注。