文章目录
ReentrantReadWriteLock
ReadWriteLock
ReadWriteLock 是一个接口,ReentrantReadWriteLock 实现了该接口的方法,除了 ReentrantReadWriteLock 之外,还有一个 StampedLock(邮戳锁) 的实现类
public interface ReadWriteLock {
// 读锁
Lock readLock();
// 写锁
Lock writeLock();
}
实际上重写的两个方法是获取 writeLock 和 readLock
public ReentrantReadWriteLock.WriteLock writeLock() {
return writerLock;
}
public ReentrantReadWriteLock.ReadLock readLock() {
return readerLock;
}
ReentrantReadWriteLock
构造方法
默认的无参构造方法使用的是非公平锁
public ReentrantReadWriteLock() {
this(false);
}
public ReentrantReadWriteLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
readerLock = new ReadLock(this);
writerLock = new WriteLock(this);
}
成员变量
主要就是这三个,定义了读锁,写锁,还有 Sync 内部类
private final ReentrantReadWriteLock.ReadLock readerLock;
private final ReentrantReadWriteLock.WriteLock writerLock;
final Sync sync;
成员方法
// 判断该 ReentrantReadWriteLock 锁是否是公平锁
public final boolean isFair() {
return sync instanceof FairSync;
}
// 获取读锁的线程总数
public int getReadLockCount() {
return sync.getReadLockCount();
}
// 判断该 ReentrantReadWriteLock 是否是写锁
public boolean isWriteLocked() {
return sync.isWriteLocked();
}
// 获取线程写锁的可重入次数
public int getWriteHoldCount() {
return sync.getWriteHoldCount();
}
// 获取线程读锁的可重入次数
public int getReadHoldCount() {
return sync.getReadHoldCount();
}
// 获取同步等待队列中写锁的线程
protected Collection<Thread> getQueuedWriterThreads() {
return sync.getExclusiveQueuedThreads();
}
// 获取同步等待队列中读锁的线程
protected Collection<Thread> getQueuedReaderThreads() {
return sync.getSharedQueuedThreads();
}
// 判断是否存在同步等待队列,即表示是否有多线程资源抢夺的情况
public final boolean hasQueuedThreads() {
return sync.hasQueuedThreads();
}
// 判断指定线程是否在同步等待队列中
public final boolean hasQueuedThread(Thread thread) {
return sync.isQueued(thread);
}
...
...
Sync
底层也是继承于 AQS,实现了 AQS 中的模板方法
成员属性
SHARED_SHIFT、SHARED_UNIT、EXCLUSIVE_MASK、MAX_COUNT
ReentrantReadWriteLock 为了提高效率节省资源,state 状态值包括了 读状态、写状态,所以需要作区分,因此使用高低位切割实现state
状态变量维护两种状态,即高16
位表示读状态,低16
位表示写状态。
// 偏移位数
static final int SHARED_SHIFT = 16;
// 1向左移16位,高16位
static final int SHARED_UNIT = (1 << SHARED_SHIFT);
// 1向左移16位减1,最大的可重入数量
static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1;
// 1向左移16位减1,低16位
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
高16位表示读状态
读锁的初始状态值 SHARED_UNIT 为:0000 0000 0000 0001 0000 0000 0000 0000,如果遇到多线程获取读锁的时候,计算方式是 compareAndSetState(c, c + SHARED_UNIT),每次都加上初始化读锁的状态值,从而获取结果;读锁的 SHARED_UNIT 在计算重入数和判断是否符合可重入的条件都起到了作用
// 1向左移16位,高16位
static final int SHARED_UNIT = (1 << SHARED_SHIFT);
低16位表示写状态
写锁初始的状态值 EXCLUSIVE_MASK 为:0000 0000 0000 0000 1111 1111 1111 1111,如果遇到多线程获取读锁的时候,计算方式是 compareAndSetState(c, c + acquires),每次都加上重入次数,从而获取结果;EXCLUSIVE_MASK 只在判断是否符合可重入条件中起作用
// 1向左移16位减1,低16位
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
HoldCounter
HoldCounter是用来记录线程可重入次数的对象
static final class HoldCounter {
int count = 0;
final long tid = getThreadId(Thread.currentThread());
}
ThreadLocalHoldCounter
ThreadLocalHoldCounter是ThreadLocal变量,用来存放第一个获取读锁线程外的其他线程的读锁重入数对象
static final class HoldCounter {
int count = 0;
final long tid = getThreadId(Thread.currentThread());
}
static final class ThreadLocalHoldCounter
extends ThreadLocal<HoldCounter> {
public HoldCounter initialValue() {
return new HoldCounter();
}
}
构造方法
很明显,在这一操作是初始化 ThreadLocalHoldCounter,然后设置 state 状态的初始化值为 0
Sync() {
readHolds = new ThreadLocalHoldCounter();
setState(getState());
}
成员方法
sharedCount(int) : int, 获取读锁的重入次数
exclusiveCount(int): int, 获取写锁的重入次数
readerShouldBlock(): boolean, 判断读操作是否需要阻塞,这是通过调用的是公平 / 非公平锁来决定的,若是非公平锁,那么读操作就会根据CLH中的同步队列是否有独占线程,若有则读阻塞;若是公平锁,那么会判断CLH同步队列中是否有其他线程,若有则读阻塞
wirterShouldBlock(): boolean, 判断写操作是否需要阻塞,这也是通过调用的是公平 / 非公平锁来决定的,若是非公平锁,那么写操作就会返回 false,因为写操作本身就是利用 AQS 的独占模式获取锁的,独占相当于阻塞;若是公平锁,那么会判断CLH同步队列中是否有其他线程,若有则读阻塞
tryRelease(int): boolean, 写锁释放锁
tryAcquire(int): boolean, 写锁获取锁
tryReleaseShared(int): boolean, 读锁释放锁
tryAcquireShared(int): boolean, 读锁获取锁
tryWriteLock(): boolean, 尝试通过写锁获取锁
tryRaadLock(): boolean, 尝试通过读锁获取锁
…
NofairSync
非公平锁,继承了父类 Sync,并且有两个方法判断读、写状态下是否会阻塞
static final class NonfairSync extends Sync {
private static final long serialVersionUID = -8159625535654395037L;
final boolean writerShouldBlock() {
return false;
}
final boolean readerShouldBlock() {
return apparentlyFirstQueuedIsExclusive();
}
}
// 这个方法就是去判断CLH队列中是否存在独占线程
final boolean apparentlyFirstQueuedIsExclusive() {
Node h, s;
return (h = head) != null &&
(s = h.next) != null &&
!s.isShared() &&
s.thread != null;
}
fairSync
公平锁,继承了父类 Sync,并且有两个方法判断读、写状态下是否会阻塞
static final class FairSync extends Sync {
private static final long serialVersionUID = -2274990926593161451L;
final boolean writerShouldBlock() {
return hasQueuedPredecessors();
}
final boolean readerShouldBlock() {
return hasQueuedPredecessors();
}
}
// 这个方法其实就是调用 AQS 的,能够判断 CLH 同步队列是否有其他线程,若有则 CLH 同步队列的线程先获取锁资源
public final boolean hasQueuedPredecessors() {
Node t = tail;
Node h = head;
Node s;
return h != t && ((s = h.next) == null || s.thread != Thread.currentThread());
}
ReadLock
读锁实现了 Lock 接口,里面的所有成员方法都是通过调用 Sync 的方法,所以也是依赖 Sync 的;除此之外,读锁是通过 AQS 的共享模式 Share 实现的,下面会有获取和释放读锁的具体流程
public static class ReadLock implements Lock, java.io.Serializable {
private final Sync sync;
protected ReadLock(ReentrantReadWriteLock lock) {
sync = lock.sync;
}
public void lock() {
sync.acquireShared(1);
}
public void lockInterruptibly() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public boolean tryLock() {
return sync.tryReadLock();
}
public boolean tryLock(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
public void unlock() {
sync.releaseShared(1);
}
public Condition newCondition() {
throw new UnsupportedOperationException();
}
}
WriteLock
写锁实现了 Lock 接口,里面的所有成员方法也都是通过调用 Sync 的方法,所以也是依赖 Sync 的;除此之外,写锁是通过 AQS 的独占模式 Exclusive 实现的
public static class WriteLock implements Lock, java.io.Serializable {
private final Sync sync;
protected WriteLock(ReentrantReadWriteLock lock) {
sync = lock.sync;
}
public void lock() {
sync.acquire(1);
}
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
public boolean tryLock( ) {
return sync.tryWriteLock();
}
public boolean tryLock(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
public void unlock() {
sync.release(1);
}
public Condition newCondition() {
return sync.newCondition();
}
public boolean isHeldByCurrentThread() {
return sync.isHeldExclusively();
}
public int getHoldCount() {
return sync.getWriteHoldCount();
}
}
ReadLock 获取读锁流程
遵循 ReentrantReadWriteLock 接口规范,首先获取读锁需要调用 ReentrantReadWriteLock.readLock()
其次 ReadLock 有两种方法可以获取到读锁:tryLock() 非阻塞方法,可以立马返回是否成功获取锁的结果;而 lock() 方法是阻塞的直到当前线程成功获取到读锁为止,在代码方面 tryLock() 和 lock() 都是大同小异的
lock()
ReadLock 调用 lock() 方法,实际上是调用了 AQS 的 acquireShared(),然后又会回调 Sync 中的对 AQS 实现的模板方法 tryAcquireShared(),具体流程如下
代码
// ReadLock中的lock方法
public void lock() {
sync.acquireShared(1);
}
// AQS中的acquireShared方法
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
// AQS中的模板方法tryAcquireShared,是在Sync中重写了
protected final int tryAcquireShared(int unused) {
// 获取当前线程
Thread current = Thread.currentThread();
// 获取当前的state状态值
int c = getState();
// exclusiveCount() 方法是获取写锁的重入次数,如果不等于0说明有写锁,再判断持有写锁的线程是不是当前线程
// 若是,则进行锁的降级(写锁降级为读锁,反之则不允许);若不是则尝试获取锁失败;如果等于0说明该线程没有写锁
// 这时就可以获取读锁
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
// 获取读锁的可重入次数
int r = sharedCount(c);
// 判断读操作是否需要阻塞,这是根据初始化Sync时定义的是公平锁还是非公平锁来决定的
// 非公平情况下,会判断CLH同步等待队列中是否有独占的线程,有则阻塞走 fullTryAcquireShared 方法
// 无则以非阻塞的方式继续执行
if (!readerShouldBlock() &&
r < MAX_COUNT &&
compareAndSetState(c, c + SHARED_UNIT)) {
// 获取成功,判断读锁的可重入次数是否为0
if (r == 0) {
// 将当前线程赋值到 firstReader,根据字面意思就是赋值给首次获取锁的线程
firstReader = current;
// 将 firstReaderHoldCount 赋值为1,表示首次获取读锁的重入次数为1
firstReaderHoldCount = 1;
// 判断当前线程 是否是 首次获取锁的线程,
} else if (firstReader == current) {
// 若是的话,firstReaderHoldCount++,重入次数加1
firstReaderHoldCount++;
// 若以上条件都不满足,表示是其他线程获取读锁
} else {
// 将每个不同的线程的可重入次数进行缓存,放入 cachedHoldCounter 中
// readHolds 为 ThreadLocalHoldCounter 对象,这个类继承了 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;
}
// 通过阻塞的方式去获取读锁,和tryAcquireShared非阻塞的方式很像,只不过使用了cas自旋获取锁
return fullTryAcquireShared(current);
}
对应的流程图
调用链如下
从以上流程可以得出
- 读读不互斥,即多个不同的线程可以同时获取读锁
- 读锁可重入,每个获取读锁的线程都会记录对应的重入数
- 支持锁降级,持有写锁的线程,可以降级为读锁,但是后续要记得把读锁和写锁读释放
- readerShouldBlock 读锁是否阻塞实现取决公平与非公平的策略(FairSync和NonfairSync)
ReadLock 释放读锁流程
ReadLock 中的释放锁是通过调用 unlock 方法执行的,获取到读锁,执行完临界区后,要记得释放读锁(如果重入多次要释放对应的次数),不然会阻塞其他线程的写操作
unlock()
代码
// ReadLock 中的 lock 方法
public void unlock() {
sync.releaseShared(1);
}
// AQS 中的 releaseShared 方法
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
// AQS 中的模板方法 tryReleaseShared,Sync 中已经对这个方法做了重写
protected final boolean tryReleaseShared(int unused) {
// 获取当前线程
Thread current = Thread.currentThread();
// 判断是否是当前线程释放锁,若是则firstReaderHoldCount重入次数-1
if (firstReader == current) {
if (firstReaderHoldCount == 1)
firstReader = null;
else
firstReaderHoldCount--;
// 若不是,则从ThreadLocal缓存中获取对应线程的可重入数,再减1
} 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;
}
// 再自旋释放当前线程的锁,返回 nextc == 0 主要是因为锁的可重入性,释放的时候必须全部释放
// 只有全部的读锁被释放了,才会去执行doReleaseShared函数
for (;;) {
int c = getState();
int nextc = c - SHARED_UNIT;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
对应的流程图
调用链如下
WriteLock 获取写锁流程
WriteLock 也实现了 lock 接口,其次 WriteLock 也有两种方法可以获取到写锁:tryLock() 非阻塞方法,可以立马返回是否成功获取锁的结果;而 lock() 方法是阻塞的直到当前线程成功获取到读锁为止,在代码方面 tryLock() 和 lock() 都是大同小异的
lock()
WriteLock 调用 lock() 方法,实际上是调用了 AQS 的 acquire(),然后又会回调 Sync 中的对 AQS 实现的模板方法 tryAcquire(),具体流程如下
代码
// WriteLock 中的 lock 方法
public void lock() {
sync.acquire(1);
}
// AQS 中的 acquire 方法
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
// AQS 中的模板方法 tryAcquire,在 WriteLock 中已经做了重写
protected final boolean tryAcquire(int acquires) {
// 获取当前线程
Thread current = Thread.currentThread();
// 获取状态值 state,包括读锁和写锁
int c = getState();
// 计算写锁的可重入次数
int w = exclusiveCount(c);
// 若状态值不为0,即有写锁或者读锁的情况下
if (c != 0) {
// 判断如果没有写锁,或者当前线程也没有持有写锁的情况下,获取锁失败,因为如果c!=0,但是当前线程又没有写锁的情况下
// 实际上是只有读锁,锁是不能够由读锁升级为写锁的
// 这里能反映出 读写互斥 写写互斥
if (w == 0 || current != getExclusiveOwnerThread())
return false;
// 若写锁的可重入数大于最大重入次数,则抛异常
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// 更新state
setState(c + acquires);
return true;
}
// 判断写操作是否阻塞,若阻塞,则获取锁失败;若不阻塞但是state状态值自旋更新失败,则获取锁也失败
// 判断写操作是否阻塞是根据 Sync 定义的公平还是非公平决定的
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))
return false;
// 获取到锁,就将当前线程设置为获取锁的线程
setExclusiveOwnerThread(current);
return true;
}
调用链如下
对应的流程图
从以上流程可以得出
- 读写互斥
- 写写互斥
- 写锁支持同一个线程重入
- writerShouldBlock写锁是否阻塞实现取决公平与非公平的策略(FairSync和NonfairSync)
WriteLock 释放写锁流程
获取到写锁,临界区执行完,要记得释放写锁(如果重入多次要释放对应的次数),不然会阻塞其他线程的读写操作,调用unlock
函数释放写锁
unlock()
// WriteLock 的方法
public void unlock() {
sync.release(1);
}
// AQS 的方法
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
// AQS 中的模板方法,在 WriteLock 进行了重写
protected final boolean tryRelease(int releases) {
// 判断持有写锁的线程是不是当前线程,若不是则抛出异常
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
// state减1
int nextc = getState() - releases;
// 判断写锁的重入次数是否已经为0
boolean free = exclusiveCount(nextc) == 0;
// 若为0,则将持有写锁的线程设置为null
if (free)
setExclusiveOwnerThread(null);
// 重新将state赋值
setState(nextc);
return free;
}