原来这叫读写锁 —— ReentrantReadWriteLock

大家好,我是徒手敲代码。

今天来介绍一下java.util.concurrent.locks包下的ReentrantReadWriteLock

顾名思义,它是读写锁的一种,同一时间,读操作可以有多个线程,但是写操作只能有一个线程,并且读和写不能同时进行;读锁被占用,那么写锁就不能获取,反过来也一样。

之前学过的互斥锁,比如ReentrantLocksynchronized,在任何时候都只允许一个线程访问共享资源,这在读操作远多于写操作的场景下,显得效率很低,因为即使多个读操作之间并不冲突,它们也必须排队等待。

ReentrantReadWriteLock的诞生,正好可以解决这个问题。它通过分离读锁和写锁,使得并发读成为可能。

下面通过阅读源码的方式,来看看大佬是如何设计这个读写锁的。

读写状态的设计

我们知道,基于 AQS 的锁实现,内部都是通过一个int类型的state变量,来维护锁的状态。Java 中,int有 32 位,而ReentrantReadWriteLock就是利用这些位来分别表示读锁和写锁的持有情况,这种设计被称为按位切割使用

读写锁将变量切分为两个部分,高16位表示读,低16位表示写,通过位运算来快速确定,读和写各自的状态。

写锁的获取和释放

写锁是一个支持重进入的排它锁。如果当前线程已经获取了写锁,则增加写状态;

如果当前线程在获取写锁时,读锁已经被获取(读状态不为0)或者该线程不是已经获取写锁的线程, 则当前线程进入等待状态。

tryAcquire这个方法:

protected final boolean tryAcquire(int acquires) {Thread current = Thread.currentThread();int c = getState();int w = exclusiveCount(c);if (c != 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;}if (writerShouldBlock() ||!compareAndSetState(c, c + acquires))return false;setExclusiveOwnerThread(current);return true;
}

这里之所以要判断是否存在读锁,因为读写锁要确保写锁的操作对读锁可见。

如果允许读锁在已被获取的情况下,还获取写锁,那么正在运行的其他读线程,就没办法感知到当前写线程的操作。因此,只有等待其他读线程都释放了读锁,写锁才能被当前线程获取,而写锁一旦被获取,则其他读写线程的后续访问都会被阻塞。

至于写锁的释放,跟ReentrantLock的释放过程基本类似,每次释放都会减少写状态,当写状态为0 时,表示写锁已被释放,等待的读写线程能够继续访问读写锁,同时之前写线程的修改,对后续读写线程可见。

读锁的获取

读锁的获取通过tryAcquireShared()方法执行,与写锁不同,只要没有写锁被持有,就可以允许多个读锁同时存在。

该方法会检查当前状态,确保没有写锁且读锁计数未达到最大限制(防止整型溢出)。成功获取后,会增加读锁的重入计数。

读锁的获取方法,代码如下:

protected final int tryAcquireShared(int unused) {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 操作),成功获取读锁。

读锁的释放

直接看tryReleaseShared方法的代码:

protected final boolean tryReleaseShared(int unused) {Thread current = Thread.currentThread();if (firstReader == current) {if (firstReaderHoldCount == 1)firstReader = null;elsefirstReaderHoldCount--;} 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;}for (;;) {int c = getState();int nextc = c - SHARED_UNIT;if (compareAndSetState(c, nextc))return nextc == 0;}
}

根据当前线程是否首次获取读锁,分别减少读锁的获取计数;然后通过 CAS 操作来释放读锁,因为可能会有多个线程同时释放读锁。

今天的分享到这里结束了。

关注公众号“徒手敲代码”,免费领取由腾讯大佬推荐的Java电子书!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/20463.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

File类(二)

遍历文件夹的功能 方法名称 说明 public String[ ] list() 获取当前目录下所有的"一级文件名称"到一个字符串数组中去返回。 public File[ ] listFiles() 获取当前目录下所有的"一级文件对象"到一个文件对象数组中去返回&#xff08;重点&#xff09;…

【问题解决】MySQL恢复数据库报错Unknown command ‘\‘‘.

问题 使用以下命令备份恢复数据库&#xff0c;恢复失败提示ERROR at line 39595: Unknown command \. #备份数据库 mysqldump -u username -p --no-create-db -R databasename > dump.sql #恢复数据库 mysql -u username -p databasename2 < dump.sql问题原因及解法 原…

css基本操作及使用

CSS 的基本简介 什么是 CSS? CSS 指层叠样式表 (Cascading Style Sheets) 样式定义如何显示 HTML 元素 样式通常存储在样式表中 把样式添加到 HTML 4.0 中&#xff0c;是为了解决内容与表现分别的问题 外部样式表可以极大提高工作效率 外部样式表通常存储在 CSS 文件中 …

轻松拿捏C语言——【内存函数】

&#x1f970;欢迎关注 轻松拿捏C语言系列&#xff0c;来和 小哇 一起进步&#xff01;✊ &#x1f389;创作不易&#xff0c;请多多支持&#x1f389; &#x1f308;感谢大家的阅读、点赞、收藏和关注&#x1f495; &#x1f339;如有问题&#xff0c;欢迎指正~~ 目录&#x1…

JVM学习-类加载过程(一)

概述 在Java中数据类型分为基本数据类型和引用数据类型&#xff0c;基本数据类型由虚拟机预先定义&#xff0c;引用数据类型则需要进行类的加载按Java虚拟机规范&#xff0c;从class文件加载到内存中的类&#xff0c;到类卸载出内存为止&#xff0c;它的整个生命周期包含以下7…

测试工具fio

一、安装部署 fio是一款优秀的磁盘IO测试工具&#xff0c;在Linux中比较常用于测试磁盘IO 其下载地址&#xff1a;https://brick.kernel.dk/snaps/fio-2.1.10.tar.gz 或者登录其官网&#xff1a;http://freshmeat.sourceforge.net/projects/fio/ 进行下载。 tar -zxvf fio-…

【redis】宝塔,线上环境报Redis error: ERR unknown command del 错误

两种方式&#xff1a; 1.打开宝塔上的redis&#xff0c;通过配置文件修改权限&#xff0c;注释&#xff1a;#rename-command DEL “” 2.打开服务器&#xff0c;宝塔中默认redis安装位置是&#xff1a;cd /www/server/redis 找到redis.conf,拉到最后&#xff0c;注释#rename-co…

Flutter 验证码输入框

前言&#xff1a; 验证码输入框很常见&#xff1a;处理不好 bug也会比较多 想实现方法很多&#xff0c;这里列举一种完美方式&#xff0c;完美兼容 软键盘粘贴方式 效果如下&#xff1a; 之前使用 uniapp 的方式实现过一次 两种方式&#xff08;原理相同&#xff09;&#xff1…

二叉树链式结构的前序、中序、后序、层序遍历

文章目录 一、二叉树创建二、前序遍历概念以及解释代码 三、中序遍历概念及解释代码 四、后序遍历概念及解释代码 五、层序遍历概念及解释代码 一、二叉树创建 &mesp; 实现二叉树的遍历&#xff0c;我们要先手搓出一个二叉树&#xff0c;在次基础上实现二叉树的前序、中序…

【RLHF个人笔记】RLHF:Reinforcement Learning from Human Feedback具体过程

【RLHF个人笔记】RLHF:Reinforcement Learning from Human Feedback具体过程 RLHF训练的三个步骤步骤1&#xff1a;收集数据与有监督训练策略步骤2&#xff1a;收集数据训练奖励模型步骤3&#xff1a;结合奖励模型利用强化学习算法如PPO算法来优化策略 参考内容 RLHF训练的三个…

今年一定要做的副业兼职,1篇文章收入600,批量操作收入翻倍

随着公众号开放公域流量&#xff0c;流量主收入迅速攀升&#xff0c;吸引了众多投资者纷纷涌入这一领域&#xff0c;通过流量主赚取了丰厚的利润。上周&#xff0c;我曾向大家介绍了一些借助公众号流量主实现盈利的策略。 然而&#xff0c;公众号的盈利途径远不止流量主一种。…

数据库(14)——DQL排序查询

DQL排序查询语法 SELECT 字段列表 FROM 表名 ORDER BY 字段1 排序方式1,字段2 排序方式2; 排序方式 ASC&#xff1a;升序 DESC&#xff1a;降序 注&#xff1a;如果是多字段排序&#xff0c;当第一个字段值相同时&#xff0c;才会根据第二个字段进行排序。如果不写排序方式默…

qcc51xx如何配置spdif输入

qcc51xx如何配置spdif输入 /* Copyright (c) 2005 - 2018 Qualcomm Technologies International, Ltd. */ /** \file \ingroup sink_app \brief This file handles all Synchronous connection messages */ /*********************************************************…

图像处理ASIC设计方法 笔记27 红外非均匀校正的两点定标校正算法

非均匀性校正(Non-Uniformity Correction, NUC)是一种在图像处理和传感器校准中常用的技术,用于改善图像传感器(如CCD或CMOS相机)的输出质量。这种校正主要针对传感器在不同像素之间可能存在的响应差异,这些差异可能是由于制造过程中的微小不完美导致的。 基本原理: 响应…

MAB规范(2):Introduction 介绍

Chapter1 Introduction 1.1 指南目的 MathWorks咨询委员会&#xff08;MAB&#xff09;指南规定了Simulink和Stateflow建模的重要基本规则。这些建模指南的总体目的是让建模者和控制系统模型的使用者能够简单、共同地理解。 指南的主要目标是&#xff1a; • 可读性  提高…

CentOS8安装opensips 3.5

环境&#xff1a;阿里云 操作系统CentOS8.5 依赖包安装&#xff1a; libmicrohttpd cd /usr/local/src wget https://ftp.gnu.org/gnu/libmicrohttpd/libmicrohttpd-latest.tar.gz tar vzxf libmicrohttpd-latest.tar.gz cd libmicrohttpd-1.0.1/./configure make make …

联芸科技偏高的关联交易:业绩波动性明显,海康威视曾拥有一票否决

《港湾商业观察》施子夫 5月31日&#xff0c;上交所上市审核委员会将召开2024年第14次审议会议&#xff0c;届时将审议联芸科技&#xff08;杭州&#xff09;股份有限公司招股书&#xff08;以下简称&#xff0c;联芸科技&#xff09;的首发上会事项。 据悉&#xff0c;此次系…

Github 2024-05-31 Java开源项目日报 Top10

根据Github Trendings的统计,今日(2024-05-31统计)共有10个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量Java项目10TypeScript项目1JavaGuide - Java 程序员学习和面试指南 创建周期:2118 天开发语言:Java协议类型:Apache License 2.0Star数量:1…

Rust 第三方库创建和导入(cargo --lib)

前言 日常开发过程中&#xff0c;难免会有一些工具方法&#xff0c;多个项目之间可能会重复使用。 所以将这些方法集成到一个第三方包中方便后期维护和管理&#xff0c; 比如工具函数如果需要修改&#xff0c;多个项目可能每个都需要改代码&#xff0c; 抽离到单独的包中只需要…

SG7050EEN差分晶体振荡器:为5G路由器提供卓越的时钟源

随着5G技术的快速发展&#xff0c;5G路由器作为连接高速网络的重要设备&#xff0c;正迅速普及。为了确保5G路由器在高宽带和低延迟的网络环境中表现出色&#xff0c;选择一款高性能的晶体振荡器至关重要。爱普生推出的SG7050EEN差分晶体振荡器&#xff0c;以其高精度、低相位噪…