深入分析实战可重入读写锁ReentrantReadWriteLock

news/2025/2/13 1:48:29/

文章目录

    • 前言
    • 加锁规则
    • 同步原理
    • 源码解析
    • 实战演示

前言

前面我们学习了可重入锁ReentrantLock,可重入锁是一个排他锁,只要不是当前线程访问加锁资源都不能够进入,只能等待锁的释放。当然,这种加锁方式也有一定的适用场景。但是,如果在读多写少的情况下可重入锁ReentrantLock可能不是那么完美,比如缓存的写入和读取。今天,我们就引出可重入读写锁ReentrantReadWriteLock,其读写分离的机制,大大提升缓存场景的系统性能。

加锁规则

可重入锁ReentrantReadWriteLock内部分为读锁和写锁,读锁与写锁的加锁规则如下:

锁类型写锁写锁
读锁共享互斥
写锁互斥互斥

同步原理

可重入锁ReentrantReadWriteLock的同步机制依然是继承抽象同步队列AQS,其内部实现了自身的读锁与写锁的规则,覆写了AQS的一些同步标识和方法。其本质上都是使用AQS同步原理,并用AQS的阻塞队列保存阻塞线程,用AQS的STATE来表示当前锁状态,可重入本质上也是加减STATE来达到对应的目的。

源码解析

进入 package java.util.concurrent.locks 查看可重入ReentrantReadWriteLock源码:

public class ReentrantReadWriteLockimplements 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 reentrantlyif (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<String,String> 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();}} 
}

http://www.ppmy.cn/news/74018.html

相关文章

nginx压测记录

nginx压测记录 1 概述2 原理3 环境3.1 设备与部署3.2 nginx配置/服务器配置 4 netty服务5 步骤6 结果7 写在最后 1 概述 都说nginx的负载均衡能力很强&#xff0c;最近出于好奇对nginx的实际并发能力进行了简单的测试&#xff0c;主要测试了TCP/IP层的长链接负载均衡 2 原理 …

【libdatachannel】1 :cmake+vs2022 构建

libdatachannel libdatachannel 是基于c++17实现的cmake 链接openssl 可以参考【libcurl 】win32 构建 Release版本 修改cmakelist 链接openssl1.1.*构建 OpenSSL 找不到 Selecting Windows SDK version 10.0.22000.0 to target Windows 10.0.22621. The CXX compiler identifi…

yolov4论文解读

数据层面上的数据增强 四张照片拼接成一张进行训练 相当于增大了batch-size&#xff0c;更适合于单GPU。 Mosaic data augmentation 马赛克数据增强 self-adversarial training(SAT) 自我对抗训练 DropBlock Label Smoothing 损失函数 由IOU改进到CIOU 网络结构 CSPNet&…

性价比提升15%,阿里云发布第八代企业级计算实例g8a和性能增强型实例g8ae

5 月 17 日&#xff0c;2023 阿里云峰会常州站上&#xff0c;阿里云正式发布第八代企业级计算实例 g8a 以及性能增强性实例 g8ae。两款实例搭载第四代 AMD EPYC 处理器&#xff0c;标配阿里云 eRDMA 大规模加速能力&#xff0c;网络延时低至 8 微秒。其中&#xff0c;g8a 综合性…

shell变量

目录 一. 总结变量的类型及含义 1. 自定义变量 2. 环境变量 3. 只读变量 4. 位置变量 5. 预定义变量 二. 实现课堂案例计算长方形面积&#xff1f;&#xff08;6种方式&#xff09; 三. 定义变量urlurlhttps://blog.csdn.net/weixin_45029822/article/details/103568815 1.…

深度学习神经网络学习笔记-多模态方向-09-VQA: Visual Question Answering

摘要 -我们提出了自由形式和开放式视觉问答(VQA)的任务。给定一张图像和一个关于图像的自然语言问题&#xff0c;任务是提供一个准确的自然语言答案。镜像现实场景&#xff0c;比如帮助视障人士&#xff0c;问题和答案都是开放式的。视觉问题有选择地针对图像的不同区域&#…

matlab结构体及其用法

MATLAB是一种很常用的科学计算软件&#xff0c;它拥有强大的数值分析和数据可视化功能。在MATLAB中&#xff0c;结构体是一种很常用的数据类型&#xff0c;它可以将不同类型的数据组合在一起&#xff0c;方便存储和处理。本文将介绍MATLAB中结构体的用法。 一、什么是MATLAB中…

访问学者J1签证面签的七个问题

作为访问学者&#xff0c;申请J1签证面签时可能会遇到一些常见问题。下面知识人网小编将介绍七个访问学者面签可能遇到的问题&#xff0c;并提供相应的答案。 问题一&#xff1a;您将在美国进行何种类型的学术研究&#xff1f; 答案&#xff1a;我将在美国从事学术研究&#x…