1、简介
1.1、场景
对共享资源有读和写的操作,且写操作没有读操作那么频繁。在没有写操作的时候,多个线程同时读一个资源没有任何问题,所以应该允许多个线程同时读取共享资源;但是如果一个线程想去写这些共享资源,就不应该允许其他线程对该资源进行读和写的操作了。
JAVA的并发包提供了读写锁ReentrantReadWriteLock,它表示两个锁,一个是读操作相关的锁,称为共享锁;一个是写相关的锁,称为排他锁
1.2、进入锁的规则
线程进入读锁的前提条件:
- 没有其他线程的写锁
- 没有写请求或者有写请求,但调用线程和持有锁的线程是同一个
线程进入写锁的前提条件:
- 没有其他线程的读锁
- 没有其他线程的写锁
1.3、读写锁的三个重要特性
- 公平选择性:支持非公平(默认)和公平的锁获取方式,吞吐量还是非公平优于公平
- 重进入:读锁和写锁都支持线程重进入
- 锁降级:遵循获取写锁、获取读锁再释放写锁的次序,写锁能够降级成为读锁。
1.4、读写锁同步状态设计
(1)获取写状态:
S&0x0000FFFF:将高16位全部抹去
(2)获取读状态:
S>>>16:无符号补0,右移16位
(3)写状态加1:
S+1
(4)读状态加1:
S+(1<<16)即S + 0x00010000
在代码层的判断中,如果S不等于0,当写状态(S&0x0000FFFF),而读状态(S>>>16)大于0,则表示该读写锁的读锁已被获取。
2、图解
2.1、继承关系图
2、重要方法调用图
从图中可见读写锁的加锁解锁操作最终都是调用ReentrantReadWriteLock
类的内部类Sync
提供的方法。Sync
对象通过继承AbstractQueuedSynchronizer
进行实现,故后续分析主要基于Sync
类进行。
3、ReadWriteLock接口介绍
内里面有2个方法的定义:readLock() 和 writeLock()
package java.util.concurrent.locks;
public interface ReadWriteLock {
//返回一个读锁
Lock readLock();
//返回一个写锁
Lock writeLock();
}
4、ReentrantReadWriteLock的成员变量与构造方法
//序列码
private static final long serialVersionUID = -6992448646407690164L;
//读锁
private final ReentrantReadWriteLock.ReadLock readerLock;
//写锁
private final ReentrantReadWriteLock.WriteLock writerLock;
//自定义同步器引用(注意,Sync只是引用,具体实现在构造方法里面为 FairSync NonFairSync)
final Sync sync;
//默认是 非公平
public ReentrantReadWriteLock() {
this(false);
}
//带参构造:true公平 false非公平
public ReentrantReadWriteLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
readerLock = new ReadLock(this);
writerLock = new WriteLock(this);
}
5、ReentrantReadWriteLock的5个内部类
分别是 Sync + FairSync + NonFairSync + ReadLock + ReadLock
5.1、Sync抽象类介绍
Sync为最重要的组成部分。其中包含了2个内部类 HoldCounter + ThreadLocalHoldCounter 。它们之和读锁有关。
FairSync 和 NonFairSync 种都只是简单的实现了抽象方法 writerShouldBlock() readerShouldBlock() 。仅仅如此!!!
5.1.1、Sync成员变量 及 构造方法
Sync
继承于AbstractQueuedSynchronizer
,其中主要功能均在AbstractQueuedSynchronizer
中完成。
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 6317671515068378041L; //版本序列号
//高16为读状态,低16位为写状态(读写锁)。SHARED_SHIFT为移动单元,方面其他计算
static final int SHARED_SHIFT = 16;
static final int SHARED_UNIT = (1 << SHARED_SHIFT); //0x0000FFFF
static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1; //读锁最大数量
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1; //排他锁(写锁)掩码
//返回读状态(共享锁)
static int sharedCount(int c) { return c >>> SHARED_SHIFT; }
//返回写状态(独占锁)
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
//注意以下4个变量之和读锁有关
//最后一个获取读锁的线程的计数器
private transient HoldCounter cachedHoldCounter;
//本地线程计数器(记录当前线程读锁获取次数)
private transient ThreadLocalHoldCounter readHolds;
//用来记录第一个获取到读锁的线程
private transient Thread firstReader = null;
//用来记录第一个获取到读锁的线程获取读锁的可重入次数
private transient int firstReaderHoldCount;
static final class HoldCounter {
int count = 0; //读锁重入的次数
//获取当前线程的ID,唯一标识一个线程
final long tid = getThreadId(Thread.currentThread());
}
static final class ThreadLocalHoldCounter extends ThreadLocal<HoldCounter> {
/**
* ThreadLocalHoldCounter重写了ThreadLocal的initialValue方法,
*ThreadLocal类可以将线程与对象相关联。
*在没有进行set的情况下,get到的均是initialValue方法里面生成的那个HolderCounter对象。
*/
public HoldCounter initialValue() {
return new HoldCounter();
}
}
Sync() { //自定义同步器构造方法
readHolds = new ThreadLocalHoldCounter(); //初始化本地线程计数器
setState(getState()); // AQS设置同步状态
}
5.1.2、重要方法
tryAcquire(int acquires) 尝试获取写锁(独占)
protected final boolean tryAcquire(int acquires) {
/*
* 1. 如果读锁不为0 | 写锁不为0且当前线程不是占有锁的线程
* 2. 如果次数超过最大值,则失败(只有count不为0才有这种情况)
* 3. 否则,如果此线程是可重入获取线程或队列策略允许的线程,则它有资格被锁定。如果是,请更新状态并设置所有者。
*/
Thread current = Thread.currentThread();
int c = getState(); //获取同步状态(32位)
int w = exclusiveCount(c); //得到写锁的状态(低16位)
if (c != 0) { //如果当前有线程占有锁
// 存在读锁 或者 当前线程不是已经获取写锁的线程 (情况1)
if (w == 0 || current != getExclusiveOwnerThread())
return false;
//执行到这里,只可能是 存在写锁,并且当前线程是获取写锁的线程
if (w + exclusiveCount(acquires) > MAX_COUNT) //写锁获取次数超过允许的最大值(情况2)
throw new Error("Maximum lock count exceeded");
//情况3
//执行到这里,已经可以正常获取锁(可重入),注意写锁不是共享锁,这里不是有竞态条件
setState(c + acquires); //设置同步状态(低16位直接加)
return true;
}
// c==0 当前还没有线程获取锁(有竞态条件,用CAS设置status)
if (writerShouldBlock() || //写锁是否应该被阻塞i,具体实现在 FairSync 和 NonFairSync中(这个实现和公平性有关)
!compareAndSetState(c, c + acquires)) //不应该阻塞,则CAS设置同步状态(可能会失败)
return false;
setExclusiveOwnerThread(current); //设置当前线程占有锁
//AQS中的exclusiveOwnerThread = thread;
return true;
}
tryRelease(int releases) 尝试释放写锁(独占锁)
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); //设置status
return free; //返回锁释放标志
}
tryAcquireShared(int unused) 尝试获取读锁(共享锁)
protected final int tryAcquireShared(int unused) {
/*
* 1. 如果另一个线程持有写锁,则失败
* 2. 读锁不被阻塞,读锁获取次数小于最大值,CAS设置同步状态成功
* 3. 步骤2失败,线程不合格(公平锁有前驱) 或者 CAS失败 或者 计数饱和
*/
Thread current = Thread.currentThread();
int c = getState();
// 情况1:写锁不为0,且不是当前线程占有(锁降级在此体现)
if (exclusiveCount(c) != 0 && getExclusiveOwnerThread() != current)
return -1;
int r = sharedCount(c); //读锁状态
//情况2
if (!readerShouldBlock() && //读锁不应该被阻塞(具体实现区分公平性)
r < MAX_COUNT && //读锁获取次数小于最大值
compareAndSetState(c, c + SHARED_UNIT)) { //CAS设置同步状态
//执行到这里,就可以获取读锁了
if (r == 0) { //当前没有线程获取读锁
firstReader = current; //当前线程为第一个获取读锁的线程
firstReaderHoldCount = 1;
} else if (firstReader == current) { //当前线程是第一个获取读锁的线程
firstReaderHoldCount++;
} else { //读锁不为空,且前线程不是第一个获取线程
HoldCounter rh = cachedHoldCounter; //缓存计数器(最后一个获取读锁的线程)
if (rh == null || rh.tid != getThreadId(current)) //缓存为空,或者当前线程不是缓存
cachedHoldCounter = rh = readHolds.get(); //获取当前线程的计数器,设置缓存计数器
else if (rh.count == 0) //如果当前线程还没有获得过读锁
readHolds.set(rh); //将当前线程计数器添加到readHolds中
rh.count++; //当前线程获取读锁次数+1
}
return 1;
}
// 情况3:情况2下线程不合格 或者 CAS失败 或者 计数饱和,则进入循环重试
return fullTryAcquireShared(current);
}
// CAS循环获取读锁,处理CAS未命中,计数饱和,线程不合格
final int fullTryAcquireShared(Thread current) {
/**此代码与tryAcquireShared中的代码部分冗余,但总体上更简单,
*因为不会使tryAcquireShared与重试和延迟读取保持计数之间的交互复杂化。
*/
HoldCounter rh = null; //缓存计数器或者当前线程计数器
for (;;) {
int c = getState();
if (exclusiveCount(c) != 0) { //写锁不为0
if (getExclusiveOwnerThread() != current) //不是当前线程获取写锁
return -1;
// 否则我们持有独占锁;在这里阻塞会导致死锁
} else if (readerShouldBlock()) { //读线程是否应该被阻塞
// 确保我们没有以可重入方式获取读锁
if (firstReader == current) {
// assert firstReaderHoldCount > 0;
} else {
if (rh == null) {
rh = cachedHoldCounter; //缓存计数器
if (rh == null || rh.tid != getThreadId(current)) {
rh = 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");
if (compareAndSetState(c, c + SHARED_UNIT)) {
//CAS设置成功
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; // 更新缓存
}
return 1;
}
}
}
回顾上面说的读锁的获取,都会包含3种情况的判断:
- 另一个线程持有写锁,失败(如果持有写锁的是本线程,可以获取,锁降级)
- 读锁不被阻塞,获取次数小于最大值,CAS设置
- 若上步失败,则进入循环CAS设置
获取读锁,修改 本地线程计数器有3种可能:
- 当前没有线程获取读锁,则设置 firstReader = current; firstReaderHoldCount = 1;
- firstReader == current
- 否则,先从 循环计数器cachedHoldCounter找,如果不是从 readHolds 种找
tryReleaseShared(int unused) 尝试释放读锁(共享锁)
//如果当前线程已经获取了读锁,则一定会释放成功(循环CAS)。返回同步状态==0
protected final boolean tryReleaseShared(int unused) {
Thread current = Thread.currentThread();
if (firstReader == current) { //如果当前线程是第一个获取到读锁的线程(效率高)
if (firstReaderHoldCount == 1)
firstReader = null;
else
firstReaderHoldCount--;
} else { //当前线程不是第一个获取读锁的线程
HoldCounter rh = cachedHoldCounter; //获取缓存计数器(最后获取读锁的线程)
//计数器为空 或者 计数器的tid不为当前正在运行的线程的tid
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; //当前获取读锁的次数-1
}
for (;;) { //循环CAS设置状态
int c = getState();
int nextc = c - SHARED_UNIT; //c-0x00010000,读状态-1
if (compareAndSetState(c, nextc)) //CAS设置同步状态
// 释放读锁,不影响其他线程的读锁
// 但如果读锁和写锁都空闲,会影响同步队列(在AQS中实现的)
return nextc == 0;
}
}
这里为什么要搞一个firstRead、firstReaderHoldCount呢?而不是直接使用else那段代码?这是为了一个效率问题,firstReader是不会放入到readHolds中的,如果读锁仅有一个的情况下就会避免查找readHolds。
HoldCounter应该就是绑定线程上的一个计数器,而ThradLocalHoldCounter则是线程绑定的ThreadLocal。从上面我们可以看到ThreadLocal将HoldCounter绑定到当前线程上,同时HoldCounter也持有线程Id,这样在释放锁的时候才能知道ReadWriteLock里面缓存的上一个读取线程(cachedHoldCounter)是否是当前线程。这样做的好处是可以减少ThreadLocal.get()的次数,因为这也是一个耗时操作。需要说明的是这样HoldCounter绑定线程id而不绑定线程对象的原因是避免HoldCounter和ThreadLocal互相绑定而GC难以释放它们 (尽管GC能够智能的发现这种引用而回收它们,但是这需要一定的代价),所以其实这样做只是为了帮助GC快速回收对象而已。
tryWriteLock() 实际上与tryAcquire相同,只是缺少对writersouldblock的调用
final boolean tryWriteLock() {
Thread current = Thread.currentThread();
int c = getState();
if (c != 0) {
int w = exclusiveCount(c);
if (w == 0 || current != getExclusiveOwnerThread())
return false;
if (w == MAX_COUNT)
throw new Error("Maximum lock count exceeded");
}
if (!compareAndSetState(c, c + 1))
return false;
setExclusiveOwnerThread(current);
return true;
}
tryReadLock() 实际上与tryAcquireShared相同,只是缺少对readersouldblock的调用
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");
if (compareAndSetState(c, c + SHARED_UNIT)) {
if (r == 0) {
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {
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;
}
}
}
5.1.3、Sync中的抽象方法(在FairSync 或者 NonFairSync 中实现)
abstract boolean readerShouldBlock();
abstract boolean writerShouldBlock();
5.1.4、其他感兴趣的方法
// 返回所有线程获取读锁的次数
final int getReadLockCount() {
return sharedCount(getState());
}
// 返回当前线程获取读锁的次数
final int getReadHoldCount() {
if (getReadLockCount() == 0)
return 0;
Thread current = Thread.currentThread();
if (firstReader == current)
return firstReaderHoldCount;
HoldCounter rh = cachedHoldCounter;
if (rh != null && rh.tid == getThreadId(current))
return rh.count;
int count = readHolds.get().count;
if (count == 0) readHolds.remove();
return count;
}
5.2、FairSync源码分析
FairSync继承Sync,在其基础上重写了2个方法
static final class FairSync extends Sync {
private static final long serialVersionUID = -2274990926593161451L;
// 两个方法都是立刻返回 boolean,判断方式是看有没有前驱节点
final boolean writerShouldBlock() {
return hasQueuedPredecessors();
}
final boolean readerShouldBlock() {
return hasQueuedPredecessors();
}
}
public final boolean hasQueuedPredecessors() {
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
5.3、NonFairSync 源码分析
和 FairSync 一样,仅仅实现了 Sync 种的两个抽象方法。
static final class NonfairSync extends Sync {
private static final long serialVersionUID = -8159625535654395037L;
// 写锁为独占锁,非公平,不用等待
final boolean writerShouldBlock() {
return false;
}
// 读锁为共享锁,非公平
final boolean readerShouldBlock() {
return apparentlyFirstQueuedIsExclusive();
}
}
final boolean apparentlyFirstQueuedIsExclusive() {
Node h, s;
return (h = head) != null &&
(s = h.next) != null &&
!s.isShared() &&
s.thread != null;
}
5.4、ReadLock 源码分析
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);
}
......
}
ReadLock里面有一个 Sync 自定义同步器。其实例是 ReentrantReadWriteLock 里面的 Sync 内部类,因此ReadLock里面的方法都是调用Sync里面的共享锁的方法。
5.5、WriteLock 源码分析
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);
}
......
}
和 ReadLock 一样,其方法实现都是调用 Sync 类的独占锁的方法。
6、ReentrantReadWriteLock 的成员变量 和 构造方法
public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializable {
private static final long serialVersionUID = -6992448646407690164L;
private final ReentrantReadWriteLock.ReadLock readerLock; //读锁
private final ReentrantReadWriteLock.WriteLock writerLock; //写锁
final Sync sync; // 自定义同步器
public ReentrantReadWriteLock() {
this(false); //调用下面那个构造方法
}
// 重要!!!
public ReentrantReadWriteLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
// 实际上就是把 ReentrantReadWriteLock的Sync赋给ReadLock的Sync了
readerLock = new ReadLock(this);
writerLock = new WriteLock(this);
}
// 两个必要的方法
public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; }
public ReentrantReadWriteLock.ReadLock readLock() { return readerLock; }
private static final sun.misc.Unsafe UNSAFE;
private static final long TID_OFFSET;
static {
try {
UNSAFE = sun.misc.Unsafe.getUnsafe();
Class<?> tk = Thread.class;
TID_OFFSET = UNSAFE.objectFieldOffset //反射方式获取
(tk.getDeclaredField("tid"));
} catch (Exception e) {
throw new Error(e);
}
}
ReentrantReadWriteLock 的成员变量非常简单,除了版本序列号、读锁ReadLock、写锁WriteLock、自定义同步器Sync(UNSAFE和TID_OFFSET先不用了解)
构造方法,默认是非公平,也可以设置公平。根据参数来实例化Sync(FairSync和NonFairSync),将Sync的实例赋给 ReadLock 和 WriteLock 的Sync
7、重要方法分析
都是一些辅助方法,没有获取、释放锁的方法。因为我们再使用时,都是通过ReentrantReadWriteLock 来获取 ReadLock 和 WriteLock类,然后通过调用 它们的获取释放锁的方法,来间接调用 ReentrantReadWriteLock 里面的 Sync 的方法。
8、使用时的方法调用路径
ReadLock 里的方法
WriteLock 里的方法
9、总结
ReentrantReadWriteLock看起来很复杂,实际上它的90%的代码都在Sync自定以同步器里面。FairSync和NonFairSync知识实现了Sync的两个抽象方法(读|写线程是否应该阻塞)。ReentrantReadWriteLock将Sync指向的实例 赋给 ReadLock和WriteLock的内部类Sync,因此ReadLock和WriteLock里面的放啊实际上就是调用 Sync 自定义同步器里面的方法。