按照类注释上的解释:ReentrantReadWriteLock是读写锁的实现,且支持ReentrantLock类似的语义即可重入。
读写锁定义:(读锁)共享锁和(写锁)排他锁,即读锁和读锁之间不互斥,写锁和谁都互斥,也就是可以共享读。因为读是不涉及修改的,不会对一致性造成影响。
所以ReentrantReadWriteLock提供了可重入机制的读锁和写锁,相对于ReentrantLock多了读锁,减小了锁的粒度。
java 并发编程系列文章目录
读锁和写锁的使用示例,即读数据的时候使用读锁,这样可以给别人读的线程留一条路,要对数据变动的时候使用写锁,保证只有一个线程修改。获取锁之后再次判断(基本使用判断逻辑)。
这是ReentrantReadWriteLock类上面注释的例子
class CachedData {
Object data;
volatile boolean cacheValid;
final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
void processCachedData() {
rwl.readLock().lock();
if (!cacheValid) {
// Must release read lock before acquiring write lock
rwl.readLock().unlock();
rwl.writeLock().lock();
try {
// Recheck state because another thread might have
// acquired write lock and changed state before we did.
if (!cacheValid) {
data = ...
cacheValid = true;
}
// Downgrade by acquiring read lock before releasing write lock
rwl.readLock().lock();
} finally {
rwl.writeLock().unlock();
// Unlock write, still hold read
}
}
try {
use(data);
} finally {
rwl.readLock().unlock();
}
}
}
可以理解为当前这把大锁细分成读锁和写锁,读锁和写锁持有sync(aqs),实现加锁和释放锁逻辑。
//读锁
private final ReentrantReadWriteLock.ReadLock readerLock;
//写锁
private final ReentrantReadWriteLock.WriteLock writerLock;
//同步器 实现了AQS的
final Sync sync;
public ReentrantReadWriteLock() {
this(false);
}
//默认非公平锁,创建读锁写锁对象传入this, 读写锁使用的是同一个sync
public ReentrantReadWriteLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
readerLock = new ReadLock(this);
writerLock = new WriteLock(this);
}
//...忽略 WriteLock 数据结构 ReadLock数据结构,下面会涉及到
从结构上看,是不是就是通过Sync 对其加锁释放锁,先把sync当成黑盒(下面会描述),也即通过aqs加锁和释放锁,如果对AQS不了解,请一定看相关文章
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 boolean isHeldByCurrentThread() {
return sync.isHeldExclusively();
}
}
可以看到 读锁使用的是sync的共享锁的获取和释放逻辑,即AQS的Shared的逻辑。所以重点看sync对于AQS的实现。
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();
}
}
在ReentrantLock里,是通过int state的从0原子设置成1代表获取成功,然后重入+1,释放-1。如果不了解请查看java并发编程 ReentrantLock详解。因为ReentrantLock就是独占锁,所以使用一个state即可,但是此时一个sync需要满足两种锁,所以把一个int值掰成两份用,高16位共享,低16位独占。
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;
/** Returns the number of shared holds represented in count */
static int sharedCount(int c) { return c >>> SHARED_SHIFT; }
/** Returns the number of exclusive holds represented in count */
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
他会调用AQS的acquireShared方法,最终会进入实现的tryAcquireShared(arg)方法
public void lock() {
sync.acquireShared(1);
}
protected final int tryAcquireShared(int unused) {
Thread current = Thread.currentThread();
int c = getState();
//如果当前有独占锁在使用,且独占锁不是自己,那加不了锁,返回-1 代表尝试加锁失败
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
//此时两种情况 1.独占锁 == 0 2.独占锁不为0,但是是自己持有锁的
int r = sharedCount(c);
//读锁是否应该被阻塞:阻塞队列里有非共享的节点,且该节点不是自己的线程。
if (!readerShouldBlock() &&
r < MAX_COUNT &&
//原子设置高16位 设置成功了线程的一些统计数据
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 1;
}
//上面没加锁成功,会进入该方法,是个for循环添加锁,前几篇文章如果都看了,会发现都是这个套路,先尝试加,不行的话for循环尝试
return fullTryAcquireShared(current);
}
final int fullTryAcquireShared(Thread current) {
HoldCounter rh = null;
for (;;) {
int c = getState();
//这段上面已描述
if (exclusiveCount(c) != 0) {
if (getExclusiveOwnerThread() != current)
return -1;
} else if (readerShouldBlock()) {
// Make sure we're not acquiring read lock reentrantly
if (firstReader == current) {
// assert firstReaderHoldCount > 0;
} else {
if (rh == null) {
//cachedHoldCounter 代表最后一个获取读锁的线程的读锁计数器
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");
//原子设置 设置成功返回1 否则从上面循环到第二步readerShouldBlock() == true 进入里面的else
if (compareAndSetState(c, c + SHARED_UNIT)) {
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
}
return 1;
}
}
}
总结一下,其实就是通过原子设置state,成功代表获取到读锁。期间的进入阻塞队列等都是基于了AQS,如果AQS不了解,请看上面相关文章
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
//AQS的方法,已经在AQS详解里详细说明,就是把等待队列的node添加到阻塞队列中去等待唤醒
doReleaseShared();
return true;
}
return false;
}
protected final boolean tryReleaseShared(int unused) {
Thread current = Thread.currentThread();
//firstReader 第一个获取读锁线程的计数器 统计数量建减1
if (firstReader == current) {
// assert firstReaderHoldCount > 0;
if (firstReaderHoldCount == 1)
firstReader = null;
else
firstReaderHoldCount--;
} 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;
}
//上述代码是非核心代码。
//释放读锁就是原子的把state减一,但是读锁是高16位,所以减SHARED_UNIT
for (;;) {
int c = getState();
int nextc = c - SHARED_UNIT;
if (compareAndSetState(c, nextc))
//只有state = 0才代表锁释放成功
return nextc == 0;
}
}
带有Interruptibly 的方法,其实就是调用AQS的带中断的方法,已经在AQS详解中详细描述。区别简单描述就是对其线程中断唤醒会做异常抛出和中断位重新中断操作
基于AQS的能力,实现了尝试加锁和尝试释放锁的方法。把state的高16位作为state使用,还是利用原子自增自减的方式去保证原子性。读锁使用的AQS里的shard方法。
java并发编程 AbstractQueuedSynchronizer(AQS)详解一
就是完全基于AQS的独占锁。
ReentrantReadWriteLock把锁的粒度划分成读锁和写锁,读锁和读锁不互斥,减小了锁粒度。在实现方面,完全基于AQS实现。相关文章已在上面列出。