前面我们学习了可重入锁ReentrantLock,可重入锁是一个排他锁,只要不是当前线程访问加锁资源都不能够进入,只能等待锁的释放。当然,这种加锁方式也有一定的适用场景。但是,如果在读多写少的情况下可重入锁ReentrantLock可能不是那么完美,比如缓存的写入和读取。今天,我们就引出可重入读写锁ReentrantReadWriteLock,其读写分离的机制,大大提升缓存场景的系统性能。
可重入锁ReentrantReadWriteLock内部分为读锁和写锁,读锁与写锁的加锁规则如下:
锁类型 | 读锁 | 写锁 |
---|---|---|
读锁 | 共享 | 互斥 |
写锁 | 互斥 | 互斥 |
可重入锁ReentrantReadWriteLock的同步机制依然是继承抽象同步队列AQS,其内部实现了自身的读锁与写锁的规则,覆写了AQS的一些同步标识和方法。其本质上都是使用AQS同步原理,并用AQS的阻塞队列保存阻塞线程,用AQS的STATE来表示当前锁状态,可重入本质上也是加减STATE来达到对应的目的。
进入 package java.util.concurrent.locks 查看可重入ReentrantReadWriteLock源码:
public class ReentrantReadWriteLock
implements ReadWriteLock, java.io.Serializable {
private static final long serialVersionUID = -6992448646407690164L;
/** Inner class providing readlock */
private final ReentrantReadWriteLock.ReadLock readerLock;
/** Inner class providing writelock */
private final ReentrantReadWriteLock.WriteLock writerLock;
/** Performs all synchronization mechanics */
final Sync sync;
/**
* Creates a new {@code ReentrantReadWriteLock} with
* default (nonfair) ordering properties.
*/
public ReentrantReadWriteLock() {
this(false);
}
/**
* Creates a new {@code ReentrantReadWriteLock} with
* the given fairness policy.
*
* @param fair {@code true} if this lock should use a fair ordering policy
*/
public ReentrantReadWriteLock(boolean fair) {
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; }
}
如上源码所示,可重入读写锁实现了读写锁ReadWriteLock,保留读写锁的机制,增加了可重入机制。调用方可以通过传入标识实现非公平锁和公平锁来保证同步,当然可重入读写锁默认NonfairSync非公平锁同步。
继续查看源码:
//内部类继承抽象同步队列
abstract static class Sync extends AbstractQueuedSynchronizer{
//加解锁省略
}
//公平锁
static final class FairSync extends Sync {
private static final long serialVersionUID = -2274990926593161451L;
final boolean writerShouldBlock() {
return hasQueuedPredecessors();
}
final boolean readerShouldBlock() {
return hasQueuedPredecessors();
}
}
//非公平锁
static final class NonfairSync extends Sync {
private static final long serialVersionUID = -8159625535654395037L;
final boolean writerShouldBlock() {
return false; // writers can always barge
}
final boolean readerShouldBlock() {
/* As a heuristic to avoid indefinite writer starvation,
* block if the thread that momentarily appears to be head
* of queue, if one exists, is a waiting writer. This is
* only a probabilistic effect since a new reader will not
* block if there is a waiting writer behind other enabled
* readers that have not yet drained from the queue.
*/
return apparentlyFirstQueuedIsExclusive();
}
}
//获取读锁
public static class ReadLock implements Lock, java.io.Serializable {
private static final long serialVersionUID = -5992448646407690164L;
private final Sync sync;
/**
* Constructor for use by subclasses
*
* @param lock the outer lock object
* @throws NullPointerException if the lock is null
*/
protected ReadLock(ReentrantReadWriteLock lock) {
sync = lock.sync;
}
//加解锁省略
}
//写锁
public static class WriteLock implements Lock, java.io.Serializable {
private static final long serialVersionUID = -4992448646407690164L;
private final Sync sync;
/**
* Constructor for use by subclasses
*
* @param lock the outer lock object
* @throws NullPointerException if the lock is null
*/
protected WriteLock(ReentrantReadWriteLock lock) {
sync = lock.sync;
}
//加解锁省略
}
如上源码所示,可重入读写锁公平与非公平锁都集成内部类Sync,本质上还是继承至AQS。ReentrantReadWriteLock的读锁与写锁都是实现Lock,并覆写了其增加解锁的方法;对于锁机制的同步规则则是直接传入内部类Sync。
由此可知,可重入读写锁ReentrantReadWriteLock的加锁方法来源是Lock,同步机制来源与抽象同步队列AQS。当然对于可重入读写锁为保证读锁与写锁、写锁与写锁的互斥,覆写了AQS的同步方法,加入了满足自身规则的一些方法。
继续查看获取锁的源码:
//尝试获取共享锁
protected final int tryAcquireShared(int unused) {
/*
* 1. 如果是写锁持有资源,其他线程直接返回失败
* 2. 如果没有资源没有加写锁,则会检查资源的锁定状态,
并尝试用cas修改state状态
* 3. 如果第二步失败,则表示当前线程没有获取锁资格或者cas修改state失败,
则该线程会重试。
*/
Thread current = Thread.currentThread();
int c = getState();
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
int r = sharedCount(c);
if (!readerShouldBlock() &&
r < MAX_COUNT &&
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;
}
return fullTryAcquireShared(current);
}
/**
* 重试获取共享锁,相比之前的方法更为简单的验证锁和cas修改state
*/
final int fullTryAcquireShared(Thread current) {
HoldCounter rh = null;
for (;;) {
int c = getState();
if (exclusiveCount(c) != 0) {
if (getExclusiveOwnerThread() != current)
return -1;
// else we hold the exclusive lock; blocking here
// would cause deadlock.
} else if (readerShouldBlock()) {
// Make sure we're not acquiring read lock reentrantly
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)) {
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;
}
}
}
如上源码所示,获取共享锁的源码主要是验证当前资源是否已经被加写锁,如果加了写锁其他线程会互斥不能获取该锁;如果没有加写锁则会进行加锁资格验证,读锁则可以共享加锁。在验证当前线程有加锁资格后,程序会用CAS修改STATE状态以保证可重入和释放锁机制的正常运行。
当然,如果当前线程之前是加了写锁,现在该线程对资源加读锁,那么此时当前写锁会进行降级为读锁。但是,反之当前线程加了读锁,然后该线程又对资源加写锁是不被支持的,因为这样会造成死锁。
虽然可重入读写锁ReentrantReadWriteLock在实际生产中使用的场景较少,但是还是有它的一席之地的。话说有需要才会存在,可重入读写锁对我们生产的缓存操作极其重要。
以下我将就缓存的读写进行代码演示,当然也是因为引入可重入读写锁会使得缓存操作效率更高。
/**
* ReentrantReadWriteLockDemo
* @author senfel
* @version 1.0
* @date 2023/5/22 10:48
*/
@Slf4j
public class ReentrantReadWriteLockDemo {
private static final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
/**
* 用内存缓存模拟
*/
private static final Map cache = new HashMap<>();
/**
* 获取缓存
* @param key
* @author senfel
* @date 2023/5/22 14:44
* @return java.lang.String
*/
public static String getValue(String key){
//加读锁获取缓存
try{
readWriteLock.readLock().lock();
String str = cache.get(key);
if(StringUtils.isNotBlank(str)){
return str;
}
}catch (Exception e){
log.error("获取缓存异常:{}",e.getStackTrace());
}finally {
readWriteLock.readLock().unlock();
}
//没有获取到写入缓存,需要加写锁
try {
readWriteLock.writeLock().lock();
//加写锁成功,再次验证是否存在缓存
String str = cache.get(key);
if(StringUtils.isNotBlank(str)){
return str;
}else{
//查询数据库获取缓存,这里模拟设置一个常量标识以获取缓存
str = "senfel";
cache.put(key,str);
return str;
}
}catch (Exception e){
log.error("获取缓存异常:{}",e.getStackTrace());
}finally {
readWriteLock.writeLock().unlock();
}
return null;
}
/**
* 设置缓存
* @param key
* @author senfel
* @date 2023/5/22 14:44
* @return java.lang.String
*/
public void setValue(String key,String value){
try{
readWriteLock.writeLock().lock();
cache.put(key,value);
}catch (Exception e){
log.error("设置缓存异常:{}",e.getStackTrace());
}finally {
readWriteLock.writeLock().unlock();
}
}
}