目录
ReentrantReadWriteLock类结构
读锁获取
读锁释放
写锁获取
写锁释放
总结
本文对java并发包中ReentrantReadWriteLock进行源码分析,我们都知道ReentrantReadWriteLock就是读写锁,其核心就是读写分离,也是基于AQS来实现的,但是它拥有两种锁实例,读锁和写锁。其中写锁是独占锁,该锁同一时间被一个线程持有。而读锁是共享锁,可以被多个线程同时使用,适用于读多写少的场景。
ReentrantReadWriteLock在持有写锁的情况下,是可以获取读锁的。而持有读锁的情况下,是不允许获取写锁的,必须先释放掉读锁再获取写锁,否则会出现死锁情况。
有关AQS可参考:AQS系列
下面进入源码分析环节,先来看一看ReentrantReadWriteLock类的结构,熟悉一下类中重要属性和构造方法
public class ReentrantReadWriteLock
implements ReadWriteLock, java.io.Serializable {
private static final long serialVersionUID = -6992448646407690164L;
/** 读锁,其实就是内部类ReadLock对象实例 */
private final ReentrantReadWriteLock.ReadLock readerLock;
/** 写锁,其实就是内部类WriteLock对象实例 */
private final ReentrantReadWriteLock.WriteLock writerLock;
/** AQS子类,上面的读锁和写锁都依赖它 */
final Sync sync;
public ReentrantReadWriteLock() {
// 默认使用非公平策略
this(false);
}
public ReentrantReadWriteLock(boolean fair) {
// 构造方法中初始化上面的sync,读锁和写锁。可以选择使用公平策略或者非公平策略
sync = fair ? new FairSync() : new NonfairSync();
readerLock = new ReadLock(this);
writerLock = new WriteLock(this);
}
public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; }
public ReentrantReadWriteLock.ReadLock readLock() { return readerLock; }
......
}
public static class ReadLock implements Lock, java.io.Serializable {
private static final long serialVersionUID = -5992448646407690164L;
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();
}
public String toString() {
int r = sync.getReadLockCount();
return super.toString() +
"[Read locks = " + r + "]";
}
}
public static class WriteLock implements Lock, java.io.Serializable {
private static final long serialVersionUID = -4992448646407690164L;
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 String toString() {
Thread o = sync.getOwner();
return super.toString() + ((o == null) ?
"[Unlocked]" :
"[Locked by thread " + o.getName() + "]");
}
public boolean isHeldByCurrentThread() {
return sync.isHeldExclusively();
}
public int getHoldCount() {
return sync.getWriteHoldCount();
}
}
从代码来看,ReentrantReadWriteLock拥有内部类ReadLock和WriteLock,对应读锁和写锁。我们讲无论是共享模式还是独占模式都是需要操作AQS中state属性。而这里的读锁跟写锁是使用的同一个AQS(Sync)实例,也就是说读锁跟写锁操作的是同一个state,然而读锁是基于AQS共享模式,写锁是基于AQS独占模式。它们对于state的操作是不一样的,这样一来不就乱套了吗,那么ReentrantReadWriteLock到底是怎么实现的呢?答案就在Sync这个类中
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 6317671515068378041L;
/*
* 下面这4个属性就是用来操作state的,ReentrantReadWriteLock把32位的state分成两部分,
* 低16位代表独占锁(WriteLock),高16位代表共享锁(ReadLock)。
*/
// 偏移量16
static final int SHARED_SHIFT = 16;
// 1左移16位,也就是2的16次方65536
// 0000 0000 0000 0001 0000 0000 0000 0000
static final int SHARED_UNIT = (1 << SHARED_SHIFT);
// 这个是65535
// 0000 0000 0000 0000 1111 1111 1111 1111
static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1;
// 发现它和上面的MAX_COUNT值一样
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
/** 将c右移16位,其实就是取c的高16位。高16位代表着共享锁(读锁)的获取次数 */
static int sharedCount(int c) { return c >>> SHARED_SHIFT; }
// 65535就是0000 0000 0000 0000 1111 1111 1111 1111 ,
// 将c按位与65535其实就是取c的低16位,代表独占锁(写锁)的重入次数
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
/**
* 每个线程持有读锁数量的计数器,保存在ThreadLocal中
*/
static final class HoldCounter {
// 持有读锁的数量
int count = 0;
// 记录当前线程id
final long tid = getThreadId(Thread.currentThread());
}
/**
* 继承自ThreadLocal并重写了父类的initialValue
*/
static final class ThreadLocalHoldCounter
extends ThreadLocal {
public HoldCounter initialValue() {
return new HoldCounter();
}
}
/**
* 将当前线程 持有 读锁的数量保存在ThreadLocal中
*/
private transient ThreadLocalHoldCounter readHolds;
/**
* 从名字来看,这是一个缓存。记录了 最后一个成功获取读锁的线程 持有读锁次数(读锁重入)。
* 这个cachedHoldCounter是和上面的readHolds配合使用的。
* 每个线程在获取到读锁后,会先从readHolds中取出holdCounter赋值给cachedHoldCounter,
* 占领cachedHoldCounter。这样就不用每次都从readHolds中去取了,降低程序开销
*/
private transient HoldCounter cachedHoldCounter;
/**
* 这个字段代表第一次获取读锁的线程
*/
private transient Thread firstReader = null;
// 上面的firstReader持有读锁的数量
private transient int firstReaderHoldCount;
Sync() {
// 初始化ThreadLocalHoldCounter
readHolds = new ThreadLocalHoldCounter();
setState(getState()); // 确保readHolds的可见性
}
/**
* Returns true if the current thread, when trying to acquire
* the read lock, and otherwise eligible to do so, should block
* because of policy for overtaking other waiting threads.
*/
abstract boolean readerShouldBlock();
/**
* Returns true if the current thread, when trying to acquire
* the write lock, and otherwise eligible to do so, should block
* because of policy for overtaking other waiting threads.
*/
abstract boolean writerShouldBlock();
}
先来看看获取读锁的过程,也就是ReadLock.lock()
public void lock() {
// 获取读锁,这里传入的arg为1
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();
// 获取state的值
int c = getState();
// exclusiveCount(c)返回state的低16位,代表独占锁,
// 如果不为0,说明有线程持有写锁。并且持有写锁的线程不是当前线程,
// 这个时候返回-1,代表获取读锁失败
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
// sharedCount(c)返回state的高16位,代表共享锁。其实就是得到 获取读锁的次数
int r = sharedCount(c);
// 如果能进入到下面这个if分支中,说明成功获取到读锁,要满足3个条件:
// 1.获取读锁的线程不需要被阻塞
// 2.获取读锁的次数不能超过65535(这里是防止溢出)
// 3.对state高16位+1,如果CAS成功,代表成功获取到读锁
if (!readerShouldBlock() &&
r < MAX_COUNT &&
compareAndSetState(c, c + SHARED_UNIT)) {
// 如果获取读锁前,state的高16位为0,
// 说明当前线程为第一个获取到读锁的线程,给firstReader和firstReaderHoldCount赋值
if (r == 0) {
firstReader = current;
firstReaderHoldCount = 1;
// 如果是firstReader发生读锁重入,firstReaderHoldCount+1
} else if (firstReader == current) {
firstReaderHoldCount++;
} else {
// cachedHoldCounter缓存的是最后一个获取到读锁的线程,
// 我们说了每个线程获取到读锁后都会占领cachedHoldCounter。
// 如果cachedHoldCounter为null或者缓存的不是当前线程,
// 那么从ThreadLocal中取出当前线程的holdCounter,将cachedHoldCounter设置为当前线程
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
cachedHoldCounter = rh = readHolds.get();
// 如果cachedHoldCounter中缓存的是当前线程,
// 并且它的count==0。再set一下readHolds
else if (rh.count == 0)
readHolds.set(rh);
// 读锁重入次数+1
rh.count++;
}
// 返回获取读锁成功
return 1;
}
// 如果上面if通过一次CAS获取读锁失败,那么这里通过CAS自璇增加获取读锁成功的几率
return fullTryAcquireShared(current);
}
// 非公平锁实现,readerShouldBlock表示有别的线程也在尝试获取锁时,是否应该阻塞
final boolean readerShouldBlock() {
// 调用了apparentFirstQueuedIsExcluisve()方法
return apparentlyFirstQueuedIsExclusive();
}
final boolean apparentlyFirstQueuedIsExclusive() {
Node h, s;
// 如果head的下一个节点是独占模式,也就是说它是来获取写锁的,
// 那么当前线程应该阻塞。让写线程先执行。
return (h = head) != null &&
(s = h.next) != null &&
!s.isShared() &&
s.thread != null;
}
// 这里就是重试的过程
final int fullTryAcquireShared(Thread current) {
HoldCounter rh = null;
for (;;) {
int c = getState();
// 1.如果有其他线程持有写锁,那么获取读锁失败
if (exclusiveCount(c) != 0) {
if (getExclusiveOwnerThread() != current)
return -1;
// 2.非公平策略
// 如果写锁没有被占用,且readerShouldBlock返回true,
// 说明等待队列(不包括head)中的第一个线程是来获取写锁的,
// 那么进入到这个if分支,处理当前线程读锁重入的场景
} else if (readerShouldBlock()) {
// 如果是当前线程读锁重入,直接往下走
if (firstReader == current) {
// assert firstReaderHoldCount > 0;
} else {
if (rh == null) {
rh = cachedHoldCounter;
// 如果cachedHoldCounter缓存的不是当前线程,
// 那么从readHolds中取出当前线程的HoldCounter
if (rh == null || rh.tid != getThreadId(current)) {
rh = readHolds.get();
// 如果count为0,说明当前线程的HoldCounter,
// 是在上面的readHolds.get()才进行初始化的,这个时候需要移除它
if (rh.count == 0)
readHolds.remove();
}
}
// 到这一步说明当前线程非读锁重入并且需要阻塞,还是乖乖去排队吧
if (rh.count == 0)
return -1;
}
}
// 读锁数量溢出
if (sharedCount(c) == MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// 如果CAS成功,代表成功的获取到了读锁,c + SHARED_UNIT也就是给state的高16位+1
if (compareAndSetState(c, c + SHARED_UNIT)) {
// 下面的处理类似tryAcquireShared
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; // cache for release
}
// 返回1,代表成功获取到锁
return 1;
}
}
}
如果当前线程获取读锁失败,那么就会调用doAcquireShared加入到AQS阻塞队列中,我们接着看获取共享锁的方法:doAcquireShared
// 这个方法会尝试获取共享锁,这里传入的arg为1
private void doAcquireShared(int arg) {
// 这里会通过自璇尝试将当前线程加入到AQS队列中,直到成功为止。
// 返回代表当前线程的node对象,并指定当前节点为共享模式
// 共享模式其实就是将nextwaiter属性设置为具体的node实例
// 而且多个线程在共享模式下,它们的nextwaiter都是指向这个node实例
// 而独占模式中,它们的nextwaiter是指向null的
// 只有在使用Condition的时候,condition队列的nextwaiter会指向它的下一个节点
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
// 注意:这里是自璇操作
for (;;) {
// 获取当前node的前一个节点
final Node p = node.predecessor();
// 如果当前节点的前一个节点是head,说明当前节点在等待队列中排在第一个
if (p == head) {
int r = tryAcquireShared(arg);
// r >= 0,说明tryAcquireShared返回1,代表当前线程成功获取到读锁
if (r >= 0) {
// 将当时线程设置为head,然后唤醒队列中后面的线程
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
// 这里判断当前线程是否需要挂起,每个线程进来waitStatus默认为0
// 这里会将当前线程前节点的waitStatus改为-1,代表当前线程需要被挂起
// 所以下一轮循环的时候,当前线程前节点的waitStatus就是-1了
// shouldParkAfterFailedAcquire返回true,跟着执行后面的parkAndCheckInterrupt
if (shouldParkAfterFailedAcquire(p, node) &&
// 挂起当前线程
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
到这里读锁获取的流程就结束了,下面我们看看读锁释放的过程:readLock().unlock
public void unlock() {
// 这里传入的arg也是1
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) {
// 如果firstReaderHoldCount为1,通过这次release以后,
// 不再持有锁,那么把firstReader置为null
if (firstReaderHoldCount == 1)
// 清理firstReader缓存
firstReader = null;
else
// 读锁重入计数-1
firstReaderHoldCount--;
} else {
HoldCounter rh = cachedHoldCounter;
// 如果cachedHoldCounter中缓存的不是当前线程,从threadLocal中取出来
if (rh == null || rh.tid != getThreadId(current))
rh = readHolds.get();
int count = rh.count;
if (count <= 1) {
// 将当前线程从threadLocal中移除,防止内存泄露。
// 因为count为1,说明经过这一次操作之后,当前线程已经完全释放了读锁
readHolds.remove();
if (count <= 0)
throw unmatchedUnlockException();
}
// 读锁重入次数-1
--rh.count;
}
// 通过CAS自璇对state的高16位-1
for (;;) {
int c = getState();
// 将state高16位-1
int nextc = c - SHARED_UNIT;
// 如果CAS成功
if (compareAndSetState(c, nextc))
// 如果nextc==0,说明state=0,也就是说此时读锁和写锁都处于空闲状态。
// 这个时候才会返回true,因为释放读锁操作对其他读线程是没有影响的。
// 这里返回true主要是帮助唤醒等待写的线程。
return nextc == 0;
}
}
在回到releaseShared这里,只有tryReleaseShared返回true才会执行doReleaseShared,此时当前线程释放读锁后,读锁和写锁都处于空闲状态。
// 释放共享锁
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
// 其实这里唤醒的获取写锁的线程
doReleaseShared();
return true;
}
return false;
}
private void doReleaseShared() {
/*
* 这里需要自璇,因为在唤醒的过程中可能又有新的线程加入进来,
* 而且CAS可能会失败
*/
for (;;) {
Node h = head;
// 判断队列是否为空
if (h != null && h != tail) {
// 获取头节点的状态
int ws = h.waitStatus;
// 线程在入队的时候会将前节点的状态设置为SIGNAL(-1)
// 所以正常来说会进入这个分支
if (ws == Node.SIGNAL) {
// 将head的waitStatus设置为0
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
// 如果head的waitStatus成功修改为0,唤醒head的下一个节点
unparkSuccessor(h);
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
// 如果执行到这里,head有变化说明线程已经被唤醒
// 那么进入下一轮循环,否则说明head没变化,退出循环
if (h == head) // loop if head changed
break;
}
}
写锁的获取类似ReentrantLock,它是独占锁。下面看下writeLock().lock
public void lock() {
sync.acquire(1);
}
public final void acquire(int arg) {
// 如果尝试获取独占锁失败,那么进入等待队列中阻塞
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
protected final boolean tryAcquire(int acquires) {
Thread current = Thread.currentThread();
int c = getState();
// 获取state的低16位,就是写锁的重入次数
int w = exclusiveCount(c);
if (c != 0) {
// 1.如果w=0并且c不等于0,说明有线程持有读锁(包括当前线程),那么获取锁失败
// 2.或者c不等于0,并且写锁被其它线程占用。也就是说有线程持有读锁
// 或者是 其他线程占据了写锁,本次获取独占锁失败
if (w == 0 || current != getExclusiveOwnerThread())
return false;
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// 写锁重入,state+1
setState(c + acquires);
return true;
}
// 如果写线程应该阻塞 或者 CAS获取独占锁失败,本次获取锁失败
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))
return false;
// 如果当前线程不需要被阻塞,并且CAS成功,设置独占锁线程为当前线程。代表获取写锁成功
setExclusiveOwnerThread(current);
return true;
}
// 非公平模式下。直接返回false,这个时候可以直接CAS去竞争锁
final boolean writerShouldBlock() {
return false; // writers can always barge
}
// 用node节点封装线程加入到队列中
private Node addWaiter(Node mode) {
// mode 传入的是Node.EXCLUSIVE,代表当前处于独占锁模式,通过构造方法传入当前线程
Node node = new Node(Thread.currentThread(), mode);
// 下面的代码尝试将当前node添加到阻塞队列的最后
Node pred = tail;
// tail尾节点不为空,代表队列不是空的
if (pred != null) {
// 将tail节点设置为当前节点的 prev节点
node.prev = pred;
//使用一次CAS尝试将当前节点设置为尾节点(tail)
if (compareAndSetTail(pred, node)) {
// CAS成功,将之前尾节点的next指向当前节点,这样就构成了双向链表。然后返回
pred.next = node;
return node;
}
}
// 如果队列为空,或者有竞争导致的CAS入队失败,那么执行enq入队操作
enq(node);
return node;
}
// 再回到acquireQueued方法,此时代表当前线程的node节点已经添加到队列中,
//接下来需要进行线程挂起,正常流程,这个方法应该返回false
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
//死循环/自旋获取锁
for (;;) {
// 获取当前node的前一个节点
final Node p = node.predecessor();
//如果p == head,即前一个节点是head,说明当前节点在阻塞队列中排在第一个,
//head通常是当前持有锁的线程,但如果head是刚刚初始化才有的,
//那说明当前head没有不属于任何线程,那么这个时候可以尝试去获取一下锁
//或者说head节点释放了锁,那么这个时候也是有机会直接获取到锁的
if (p == head && tryAcquire(arg)) {
//将当前线程设置为head
setHead(node);
p.next = null; // help GC
failed = false;
//成功获取到锁直接返回
return interrupted;
}
//如果当前node不是阻塞队列的第一个节点 或者尝试获取锁失败,会执行下面的逻辑
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
如果tryAcquire失败,加入到AQS等待队列中,等待获取锁。接下来再看写锁的释放过程: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)
// 唤醒head下一个线程,也就是等待队列(不包括head)中的第一个节点
unparkSuccessor(h);
return true;
}
return false;
}
// 释放锁,也就是执行state-1
protected final boolean tryRelease(int releases) {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
// state-1后的值
int nextc = getState() - releases;
// 判断state的低16位是否为0,也就是写锁是否释放成功
boolean free = exclusiveCount(nextc) == 0;
if (free)
setExclusiveOwnerThread(null);
// 设置state的值
setState(nextc);
// 返回写锁是否释放成功
return free;
}
最后我们来总结一下,ReentrantReadWriteLock的精髓就在于读写分离,将state分为高16位和低16位,对应于读锁和写锁。
如果有线程持有了写锁,那么其它线程无法获取写锁与读锁,但是持有写锁的线程可以再次获取读锁(锁降级)。如果有线程持有了读锁。那么所有线程不能够获取到写锁(包括持有读锁的线程),但是其他线程可以继续获取读锁。
下面我们来看下非公平策略下的流程: