- 引用自JDK文档的简介
ReadWriteLock 维护了一对锁,一个用于只读操作,另一个用于写入操作。只要没有 writer,读取锁可以由多个 reader 线程同时保持。写入锁是独占的。
所有 ReadWriteLock 实现都必须保证 writeLock 操作的内存同步效果也要保持与相关 readLock 的联系。也就是说,成功获取读锁的线程会看到写入锁之前版本所做的所有更新。
与互斥锁相比,读-写锁允许对共享数据进行更高级别的并发访问。虽然一次只有一个线程(writer 线程)可以修改共享数据,但在许多情况下,任何数量的线程可以同时读取共享数据(reader 线程),读-写锁利用了这一点。从理论上讲,与互斥锁相比,使用读-写锁所允许的并发性增强将带来更大的性能提高。在实践中,只有在多处理器上并且只在访问模式适用于共享数据时,才能完全实现并发性增强。
- 类关系图
- 结合关系图来看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;
/** 同步机制类 */
final Sync sync;
// 省略 。。。。。。
// 初始化
public ReentrantReadWriteLock() {
this(false);
}
//默认是非公平实现
public ReentrantReadWriteLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
readerLock = new ReadLock(this);
writerLock = new WriteLock(this);
}
// 省略 。。。。。。
}
public static class ReadLock implements Lock, java.io.Serializable {
private static final long serialVersionUID = -5992448646407690164L;
private final Sync sync;
// 读锁的实现依赖于ReentrantReadWriteLock中的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;
// 写锁的实现依赖于ReentrantReadWriteLock中的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();
}
}
- ReadLock和WriteLock这两个类都实现了Lock接口,从源码中可以看出,读锁、写锁的操作都是依靠NonfairSync类来实现的,NonfairSync继承自Sync,Sync又继承自AbstractQueuedSynchronizer。
- 我们都知道基于AQS的一系列锁的实现都与CAS计算status值有关,那ReadWriteLock分为读写两种类型,如何用一个int型的变量来记录同步状态呢???
- 读锁和写锁共用一个int型的status字段,作者使用"按位切割"来共用一个字段,在java中int是32位的二进制表示,所以共享读锁用32位中的前16位,独占写锁用32位中的后16位具体是如何分割?如何计算?如何统计数量?在看过源码之后再具体介绍
- Sync中比较重要的几个变量
static final int SHARED_SHIFT = 16;
static final int SHARED_UNIT = (1 << SHARED_SHIFT);
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; }
- 不明白为什么直接介绍tryAcquireShared和tryAcquire的小伙伴可以看一下 AQS框架独占锁详解
// 尝试获取读共享锁
protected final int tryAcquireShared(int unused) {
// 获取当前线程
Thread current = Thread.currentThread();
// 获取status当前同步状态值
int c = getState();
// 判断是否有写独占锁 && 获取写独占锁的线程是不是当前线程
if (exclusiveCount(c) != 0 && getExclusiveOwnerThread() != current)
// 说明当前存在写独占锁,读共享锁获取失败直接返回
return -1;
// 获取共享锁的数量
int r = sharedCount(c);
// 判断读锁是否要阻塞 && 当前共享锁数量是否大于最大数量 && CAS改变同步状态是否成功
if (!readerShouldBlock() &&
r < MAX_COUNT &&
compareAndSetState(c, c + SHARED_UNIT)) {
if (r == 0) {
// 记录没有共享读锁的首个线程
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {
// 首个共享读锁重入,数量自增1
firstReaderHoldCount++;
} else {
// 将当前共享读锁的数量绑定到ThreadLocal中
// HoldCounter类只有两个字段 { 线程读锁计数, 读锁线程id }
// readHolds是ThreadLocalHoldCounter类型继承自ThreadLocal
// 所有共享读线程的HoldCounter对象绑定在ThreadLocal中
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
// readHolds.get()获取来的是一个初始的HoldCounter实例
// cachedHoldCounter 是缓存的一个成员HoldCounter类实例
cachedHoldCounter = rh = readHolds.get();
else if (rh.count == 0)
// rh不等于null && count == 0
readHolds.set(rh);
// 自增
rh.count++;
}
return 1;
}
return fullTryAcquireShared(current);
}
// Sync初始化时构造ThreadLocalHoldCounter实例
Sync() {
readHolds = new ThreadLocalHoldCounter();
setState(getState()); // ensures visibility of readHolds
}
static final class ThreadLocalHoldCounter
extends ThreadLocal<HoldCounter> {
// ThreadLocal初始化时会调用子类实现方法initialValue,将HoldCounter实例存入ThreadLocal
public HoldCounter initialValue() {
return new HoldCounter();
}
}
// 尝试获取写独占锁
protected final boolean tryAcquire(int acquires) {
// 获取当前线程
Thread current = Thread.currentThread();
// 获取同步状态
int c = getState();
// 获取独占写锁数量
int w = exclusiveCount(c);
if (c != 0) {
// if c != 0 and w == 0 then shared count != 0
// status!=0说明有线程获取了锁
// w=0说明获取的锁不是写锁
// 那么则说明读锁已被获取,有任何一把锁的情况下都要阻塞
if (w == 0 || current != getExclusiveOwnerThread())
return false;
// 判断写锁是否超过最大数量
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// 如果是同一线程重入锁的话,修改同步状态中的数量
setState(c + acquires);
return true;
}
// c = 0 说明没有任何锁,尝试CAS(status)
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))
return false;
// 设置独占锁对应线程为自己
setExclusiveOwnerThread(current);
return true;
}
-
SHARED_SHIFT = 16
- exclusiveCount() 讲解
// 获取独占写锁数量
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
status & (1 << SHARED_SHIFT) - 1;
如果读锁与写锁同时存在,如下
# 注意:这里只是举个例子,假如存在这种情况,为了方便讲解同步状态
0000 0000 0000 0001 0000 0000 0000 0001
说明有1个读锁1个写锁
当程序要获取写独占锁锁时
因为0&1=0,1&1=1,所以
0000 0000 0000 0001 0000 0000 0000 0001
&
0000 0000 0000 0000 1111 1111 1111 1111
=
0000 0000 0000 0000 0000 0000 0000 0001
即只有1个独占写锁,存在共享读锁时并不影响获取独占写锁,前16位与后16位完全不相关。
- sharedCount() 讲解
// 获取共享读锁数量
// >>表示右移,如果该数为正,则高位补0,若为负数,则高位补1;
// >>>表示无符号右移,也叫逻辑右移,即若该数为正,则高位补0,而若该数为负数,则右移后高位同样补0。
// 对于正数而言,>>和>>>没区别,左移没有<<<运算符
获取共享锁数量static int sharedCount(int c) { return c >>> SHARED_SHIFT; }
c右移16位是为了获取前16位
例如只有1个共享锁时
二进制为 0000 0000 0000 0001 0000 0000 0000 0000
十进制为 65536
二进制右移16位 0000 0000 0000 0000 0000 0000 0000 0001
右移运算后结果为1,得到1个共享读锁
- readLock tryAcquireShared 更新status时,compareAndSetState(c, c + SHARED_UNIT) 讲解
readLock tryAcquireShared 更新status时,将当前status+(1 << SHARED_SHIFT)是因为什么
因为读锁用32位中的前16位,写锁用32位中的后16位
所以tryAcquireShared方法中更新status变量必须写为当前status+(1 << SHARED_SHIFT)
1左移16位运算得65536(2的16次方),对应32位二进制是0000 0000 0000 0001 0000 0000 0000 0000
status+65536的目的是只操作32位中的前16位
此时的status=65536,那么第二线程获取读锁后就变成了65536+65536(其实就是前16位的1+1操作),以此类推