关联文章:
关联文章:
Java并发源码分析之AQS及ReentrantLock
Java并发源码分析之Semaphore
Java并发源码分析之Condition
Java并发源码分析之CyclicBarrier
Java并发源码分析之CountDownLatch
目录
工作原理概要
ReentrantReadWriteLock是基于AQS框架构建的,相关类图如下:
类图
AbstractOwnableSynchronizer:抽象类,定义了独占锁线程,exclusiveOwnerThread
AbstractQueuedSynchronizer:抽象类,继承了AbstractOwnableSynchronizer,里面包含内部类Node
ReentrantReadWriteLock:类,里面包含五个内部类Sync、NonfairSync、FairSync、ReadLock、WriteLock
Sync:类,继承了AbstractQueuedSynchronizer,重写了tryAcquire、tryRelease、tryAcquireShared、tryReleaseShared方法,定义了readerShouldBlock、writerShouldBlock抽象方法
NonfairSync:类,继承了Sync,重写了readerShouldBlock、writerShouldBlock
FairSync:类,继承了Sync,重写了readerShouldBlock、writerShouldBlock
ReadLock:类,继承了Lock,重写了lock、tryLock、unlock
WriteLock:类,继承了Lock,重写了lock、tryLock、unlock
特性
1、不同线程间写写互斥、读写互斥、读读共享
2、同一线程持有写锁,可以再申请写锁,可重入;持有写锁,可以再申请读锁,称之为锁降级;
3、同一线程持有读锁,所有线程都可以再申请读锁,但是所有线程不能再申请写锁(包括当前线程)
原理概述
ReentrantReadWriteLock是基于AQS框架实现的,做到了可重入、可中断,有公平锁和非公平锁的各自实现,也有读锁和写锁的实现。 在使用时,读写锁持有的是一个锁实例,对于写少读锁的场景,可以提高效率。
在ReentrantReadWriteLock中,会将AQS中的同步状态变量state的高16位和低16位进行拆分,高16位记录读锁数量, 低16位记录写锁数量。
Sync
ReentrantReadWriteLock的核心代码都在类Sync中,这里先简单解析一下它
关键常量
// 共享锁偏移量
static final int SHARED_SHIFT = 16;
// 共享锁的位置, 1左移16位, 即共享锁数量变动单位
static final int SHARED_UNIT = (1 << SHARED_SHIFT);
// 允许申请锁的最大数量
static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1;
// 得到低16位的补码, 这样的int值EXCLUSIVE_MASK的高16位都是0, 低16位都是1
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
内部类
// 线程读锁计数器,用于每个线程持有读锁的计数
static final class HoldCounter {
// 每个线程持有读锁的数量
int count = 0;
// 当前持有读锁的线程id
// 这里使用线程ID而没有使用引用, 避免垃圾收集器保留引用, 导致无法回收
final long tid = getThreadId(Thread.currentThread());
}
// 通过ThreadLocal维护每个线程的HoldCounter
static final class ThreadLocalHoldCounter
extends ThreadLocal<HoldCounter> {
public HoldCounter initialValue() {
return new HoldCounter();
}
}
关键变量
// 当前线程持有的可重入读锁的数量, 仅在构造方法和readObject方法中被初始化
// 当持有锁的数量为0时, 移除此对象
private transient ThreadLocalHoldCounter readHolds;
// 成功获取到读锁的最近一个线程的计数器
private transient HoldCounter cachedHoldCounter;
// 首个获取读锁的线程
private transient Thread firstReader = null;
// 首个获取读锁线程持有读锁的数量
private transient int firstReaderHoldCount;
读锁/写锁计算方法
/**
* 对于AQS的state值, 存储的是一个32位int, ReentrantReadWriteLock将高16位记录读锁数量, 低16位记录写锁数量
* sharedCount, 传入的c是state, 通过无符号右移16位, 得到的是高16位, 也就是读锁/共享锁的数量
* exclusiveCount, 传入的c也是state, 通过和EXCLUSIVE_MASK进行位与运算, 得到的是低16位, 也就是写锁/排他锁的数量
*/
static int sharedCount(int c) { return c >>> SHARED_SHIFT; }
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
ReadLock获取读锁流程
1、ReadLock–lock获取读锁入口
// 获取读锁
public void lock() {
sync.acquireShared(1);
}
2、AQS–acquireShared获取共享锁
public final void acquireShared(int arg) {
// 尝试获取共享锁
if (tryAcquireShared(arg) < 0)
// 获取失败,将线程加入同步队列
doAcquireShared(arg);
}
3、Sync–tryAcquireShared尝试获取共享锁
protected final int tryAcquireShared(int unused) {
// 获取当前线程
Thread current = Thread.currentThread();
// 获取state
int c = getState();
// 如果存在写锁, 并且持有写锁的线程不为当前线程, 返回-1, 获取读锁失败
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
// 走到这里说明, 不存在写锁, 或者是当前线程持有的写锁
// 获取读锁数量
int r = sharedCount(c);
// readerShouldBlock, 判断当前线程申请读锁是否需要被阻塞, 它在FairSync和NonfairSync中的实现不相同
// 在FairSync中, 如果同步队列不为空, 且有效的第一个节点不为当前线程, 即队列中有其他线程在等待获取读锁, 则需要阻塞
// 在NonfairSync中, 如果队列不为空, 且队列中第一个节点的线程在等待获取写锁, 则需要阻塞
// 如果不需要阻塞, 且读锁数量小于最大读锁数量, 通过CAS将读锁数量+1
if (!readerShouldBlock() &&
r < MAX_COUNT &&
compareAndSetState(c, c + SHARED_UNIT)) {
// 走到这里表示添加读锁成功
// r为0, 表示当前线程添加读锁之前, 没有其他线程持有读锁
if (r == 0) {
// firstReader赋值为当前线程
firstReader = current;
// firstReaderHoldCount赋值为1
firstReaderHoldCount = 1;
} else if (firstReader == current) {
// 如果读锁数量不为0, 并且firstReader为当前线程, firstReaderHoldCount+1
firstReaderHoldCount++;
} else {
// 走到这里说明, 读锁数量不为0, 且首个获取到读锁的线程不为当前线程
// 获取最近一次获取到读锁的线程计数器
HoldCounter rh = cachedHoldCounter;
// 如果计数器为null, 或者计数器的线程id不为当前线程
if (rh == null || rh.tid != getThreadId(current))
// 从ThreadLocal中获取当前线程的计数器
// 将最近一次获取到读锁的线程计数器赋值为当前计数器
cachedHoldCounter = rh = readHolds.get();
else if (rh.count == 0)
// 走到这里说明, 最近一次获取到读锁的线程是当前线程, 但是count为0, 表示当前线程在获取到读锁之后又释放了, readHolds也被移除了
// 重新设置本地线程变量readHolds
readHolds.set(rh);
// 累加计数器中读锁数量
rh.count++;
}
return 1;
}
// 走到这里说明, 当前线程需要被阻塞, 或持有读锁的数量超多最大值, 或通过CAS更新读锁数量失败
// 通过自旋解决获取读锁失败的情况
return fullTryAcquireShared(current);
}
4、Sync–readerShouldBlock判断获取读锁是否应该被阻塞
// 非公平锁实现
final boolean readerShouldBlock() {
return apparentlyFirstQueuedIsExclusive();
}
final boolean apparentlyFirstQueuedIsExclusive() {
Node h, s;
// 同步队列不为空,且队列中的节点为独占节点
return (h = head) != null &&
(s = h.next) != null &&
!s.isShared() &&
s.thread != null;
}
// 公平锁实现
final boolean readerShouldBlock() {
return hasQueuedPredecessors();
}
public final boolean hasQueuedPredecessors() {
Node t = tail;
Node h = head;
Node s;
// 同步队列不为空,并且第一个有效节点不为当前线程
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
5、Sync–fullTryAcquireShared继续尝试获取读锁
final int fullTryAcquireShared(Thread current) {
HoldCounter rh = null;
// 自旋
for (;;) {
// 获取state
int c = getState();
// 存在排它锁
if (exclusiveCount(c) != 0) {
// 不是当前线程持有的排它锁, 无法申请读锁, 直接返回失败
if (getExclusiveOwnerThread() != current)
return -1;
// 如果当前线程申请读锁应该被阻塞
} else if (readerShouldBlock()) {
// 以下操作,官方解释是为了确保我们没有以可重入方式获取读锁
if (firstReader == current) {
} else {
// 由于是自旋操作, 第一次循环是rh为null, 将rh赋值为最近一次获取到读锁的计数器
if (rh == null) {
rh = cachedHoldCounter;
// 如果rh为null或者计数器的线程id不为当前线程
// rh为null,则最近一次获取到读锁的线程释放了读锁
if (rh == null || rh.tid != getThreadId(current)) {
// 将rh赋值为当前线程
rh = readHolds.get();
// 如果当前线程持有的读锁数量为0, 则将当前线程移除
if (rh.count == 0)
readHolds.remove();
}
}
// 第一次循环之后, rh赋值为了当前线程, 如果当前线程持有的读锁数量为0, 返回获取读锁失败
if (rh.count == 0)
return -1;
}
}
// 读锁线程等于最大值, 抛出异常
if (sharedCount(c) == MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// 走到这里说明, 分为一下几个情况:
// 1、存在排它锁, 但是持有排它锁的线程为当前线程
// 2、不存在排它锁,当前线程应该被阻塞, 但是首个获取到读锁的线程为当前线程
// 3、不存在排它锁,当前线程应该被阻塞, 首个获取到读锁的线程不是当前线程,但是当前线程持有读锁,现在是可重入申请读锁
// 3、不存在排它锁, 当前线程不被阻塞, 仅是上次CAS操作未成功
// 通过CAS获取读锁
if (compareAndSetState(c, c + SHARED_UNIT)) {
// 读锁数量为0
if (sharedCount(c) == 0) {
// 设置当前线程为首个获取到读锁的线程
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {
// 首个获取到读锁的线程是当前线程, 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
}
return 1;
}
}
}
通过自旋来申请读锁,先判断是否有排它锁(写锁)的存在,如果存在,判断是否是当前线程,如果为当前线程,可以继续申请读锁。如果不是,直接返回获取读锁失败。
然后判断是否需要阻塞,以公平锁为例,需要阻塞,说明队列中有节点,且第一个有效节点为排他节点。如果当前线程是首个获取到读锁的线程,或者当前持有读锁,则可以继续申请读锁。如果都不是,直接返回获取读锁失败。
如果不需要阻塞,可以继续申请读锁,一直循环申请,直到申请成功,返回成功。或者读锁超过最大数量,抛出异常。
6、AQS–doAcquireShared尝试获取共享锁失败,加入同步队列
// 共享锁的获取
private void doAcquireShared(int arg) {
// 将当前线程封装成共享节点并放入队列的尾部
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
// 自旋
for (;;) {
// 获取node的前驱节点
final Node p = node.predecessor();
// 前驱节点为头节点
if (p == head) {
// 尝试获取共享锁
int r = tryAcquireShared(arg);
if (r >= 0) {
// 获取成功, 将前驱节点从等待队列中释放, 同时唤醒前驱节点的后继节点中的线程
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
// 当前线程的节点不是头节点, 或者不可以获取读锁
// shouldParkAfterFailedAcquire, 检查当前节点在获取锁失败后释放需要被阻塞, 如果需要, 执行parkAndCheckInterrupt方法阻塞当前线程
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
将当前线程封装成共享节点加入队列等待,非第一个有效节点,则将线程挂起
ReadLock释放读锁流程
1、ReadLock–unlock释放读锁入口
public void unlock() {
sync.releaseShared(1);
}
2、AQS–releaseShared释放共享锁
public final boolean releaseShared(int arg) {
// 尝试释放共享锁,返回释放读锁的线程是否不再持有锁
if (tryReleaseShared(arg)) {
// 不再持有锁,唤醒同步队列中的节点
doReleaseShared();
return true;
}
return false;
}
3、Sync–tryReleaseShared尝试释放共享锁
protected final boolean tryReleaseShared(int unused) {
// 获取当前线程
Thread current = Thread.currentThread();
// 如果当前线程是首个获取到读锁的线程
if (firstReader == current) {
// assert firstReaderHoldCount > 0;
// 首个获取到读锁的线程的读锁数量为1, 则设置为null
if (firstReaderHoldCount == 1)
firstReader = null;
else
// 否则-1
firstReaderHoldCount--;
} else {
// 获取最近一个获取到读锁的计数器
HoldCounter rh = cachedHoldCounter;
// 如果最近获取到读锁的线程不是当前线程
if (rh == null || rh.tid != getThreadId(current))
// rh设置为当前线程的计数器
rh = readHolds.get();
int count = rh.count;
// 如果计数小于等于1, 释放之后不再持有读锁, 则将本地线程变量readHolds删除
if (count <= 1) {
readHolds.remove();
// 如果计数<=0,说明已经不再持有读锁,释放抛出异常
if (count <= 0)
throw unmatchedUnlockException();
}
// 计数-1
--rh.count;
}
// 自旋
for (;;) {
int c = getState();
int nextc = c - SHARED_UNIT;
// 将state的读锁数量-1
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.
// 返回读锁数量是否为0
return nextc == 0;
}
}
4、AQS–doReleaseShared唤醒同步队列中的节点
private void doReleaseShared() {
// 自旋
for (;;) {
Node h = head;
// 同步队列不为空
if (h != null && h != tail) {
// 获取头节点的等待状态
int ws = h.waitStatus;
// 如果等待状态为等待唤醒
if (ws == Node.SIGNAL) {
// 更新等待状态为0
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue;
// 唤醒头节点的后继节点
unparkSuccessor(h);
}
// 如果ws=0,说明有线程释放共享锁之后,更新了头节点的等待状态,并且唤醒了后继节点
// 但是后继节点还未获取到共享锁,又一个线程释放了共享锁
// 通过CAS将ws更新成更新成Node.PROPAGATE(-3)
// 如果后继节点获取共享锁失败(即又有两个线程获取到共享锁),还是会将ws更新成Node.SIGNAL(shouldParkAfterFailedAcquire方法里)
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue;
}
// 如果头节点发生变化,跳出循环
// 释放锁之后会有其他线程获取到锁,如果是头节点的后继节点,该节点再获取到锁之后会更新头节点
if (h == head)
break;
}
}
WriteLock获取写锁流程
1、WriteLock–lock获取写锁入口
public void lock() {
sync.acquire(1);
}
2、AQS–acquire获取排它锁
public final void acquire(int arg) {
// 尝试获取排它锁
if (!tryAcquire(arg) &&
// 尝试获取排它锁失败,则封装为排它节点加入同步队列
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
addWaiter、acquireQueued方法和ReentrantLock中的解析相同,这里不做赘述
3、Sync–tryAcquire尝试获取排它锁
protected final boolean tryAcquire(int acquires) {
// 获取当前线程
Thread current = Thread.currentThread();
// 获取state的值
int c = getState();
// 获取当前写锁的可重入数量
int w = exclusiveCount(c);
// 如果state的值不为0, 表示有线程持有锁, 读锁或者写锁
if (c != 0) {
// 如果写锁的可重入数量为0, 说明其他线程持有的是读锁, 当前线程无法获取写锁
// 如果独占锁线程不为当前线程, 当前线程也无法获取写锁
if (w == 0 || current != getExclusiveOwnerThread())
return false;
// 如果写锁可重入数量超过最大数量, 抛异常
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// 走到这里说明是当前线程已经持有写锁, 只需将state加acquires即可, 返回成功
setState(c + acquires);
return true;
}
// 走到这里说明, state为0, 没有线程持有锁
// 判断获取写锁线程是否需要阻塞,不需要阻塞则通过CAS将state的值加acquires, 如果设置失败, 返回加写锁失败
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))
return false;
// 走到这里说明加锁成功, 将独占锁线程设置为当前线程
setExclusiveOwnerThread(current);
// 返回加锁成功
return true;
}
4、Sync–writerShouldBlock判断获取写锁线程是否需要阻塞
// 非公平锁实现
final boolean writerShouldBlock() {
// 直接返回false,不需要阻塞
return false;
}
// 公平锁实现
final boolean writerShouldBlock() {
// 判断同步队列不为空,且第一个有效节点的线程不为当前线程
return hasQueuedPredecessors();
}
public final boolean hasQueuedPredecessors() {
Node t = tail;
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
WriteLock释放写锁流程
1、WriteLock–unlock释放写锁入口
public void unlock() {
sync.release(1);
}
2、AQS–release释放排它锁
public final boolean release(int arg) {
// 尝试释放排它锁,返回排它锁是否完全释放,即持有排它锁的线程不再持有
if (tryRelease(arg)) {
// 获取头节点
Node h = head;
// 头节点不为空,且头节点的等待状态不为0,唤醒头节点的后继节点
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
unparkSuccessor之前的博客介绍过,这里不做赘述。
3、Sync–tryRelease尝试释放写锁
protected final boolean tryRelease(int releases) {
// 判断持有写锁的线程是否是当前线程, 如果不是, 直接抛出异常, 防止其他线程非法操作
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
// 走到这里说明, 是持有写锁的线程在操作释放锁, 计算释放之后state的值
int nextc = getState() - releases;
// 判断写锁的可重复数量释放之后是否为0
boolean free = exclusiveCount(nextc) == 0;
// 为0, 则将独占锁线程设置为null
if (free)
setExclusiveOwnerThread(null);
// 将state的值更新
setState(nextc);
// 返回写锁是否完全释放
return free;
}
ReadLock尝试获取读锁流程
1、ReadLock–tryLock尝试获取读锁入口
public boolean tryLock() {
return sync.tryReadLock();
}
2、Sync–tryReadLock尝试获取读锁
final boolean tryReadLock() {
// 获取当前线程
Thread current = Thread.currentThread();
// 自旋
for (;;) {
// 获取同步状态
int c = getState();
// 如果存在写锁,并且独占锁线程变量不为当前线程,直接返回失败
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return false;
// 获取读锁数量
int r = sharedCount(c);
// 读锁数量等于最大值,抛出异常
if (r == MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// 通过CAS更新同步状态,更新成功即获取读锁成功
// 更新失败则再下次循环中继续更新
if (compareAndSetState(c, c + SHARED_UNIT)) {
// 获取读锁前,读锁为0,将首个获取读锁的线程赋值为当前线程
if (r == 0) {
firstReader = current;
// 首个获取读锁的线程持有读锁数量赋值为1
firstReaderHoldCount = 1;
// 如果首个获取读锁的线程为当前线程
} else if (firstReader == current) {
// firstReaderHoldCount+1
firstReaderHoldCount++;
} else {
// 走到这里说明首个获取读锁的线程不是当前线程
// 设置缓存计数器,跟上面介绍过的一致
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 true;
}
}
}
WriteLock尝试获取写锁流程
1、WriteLock–tryLock尝试获取写锁入口
public boolean tryLock( ) {
return sync.tryWriteLock();
}
2、Sync–tryWriteLock尝试获取写锁
final boolean tryWriteLock() {
// 获取当前线程
Thread current = Thread.currentThread();
// 获取当前state值
int c = getState();
// 如果有线程持有锁
if (c != 0) {
// 获取写锁的可重入数量
int w = exclusiveCount(c);
// 写锁的可重入数量为0, 其他线程持有读锁; 或当前线程非持有写锁的线程, 返回失败
if (w == 0 || current != getExclusiveOwnerThread())
return false;
// 写锁的可重入数量超过最大写锁数量
if (w == MAX_COUNT)
throw new Error("Maximum lock count exceeded");
}
// 走到这里说明, 没有线程持有锁, 通过CAS将state+1, 失败则返回加锁失败
if (!compareAndSetState(c, c + 1))
return false;
// 设置独占锁线程为当前线程
setExclusiveOwnerThread(current);
// 返回获取成功
return true;
}