读写锁ReentrantReadWriteLock的原理

解决线程安全问题使用ReentrantLock就可以,但是ReentrantLock是独占锁,某时只有一个线程可以获取该锁,而实际中会有写少读多的场景,显然ReentrantLock满足不了这个需求,所以ReentrantReadWriteLock应运而生。

ReentrantReadWriteLock采用读写分离的策略,允许多个线程可以同时获取读锁。

类图结构

为了了解ReentrantReadWriteLock的内部构造,我们先看下它的类图结构。

在这里插入图片描述

读写锁的内部维护了一个ReadLock和一个WriteLock,它们依赖Sync实现具体功能。

而Sync继承自AQS,并且也提供了公平和非公平的实现。

下面只介绍非公平的读写锁实现。我们知道AQS中只维护了一个state状态,而ReentrantReadWriteLock则需要维护读状态和写状态,一个state怎么表示写和读两种状态呢?ReentrantReadWriteLock巧妙地使用state的高16位表示读状态,也就是获取到读锁的次数;使用低16位表示获取到写锁的线程的可重入次数。

在这里插入图片描述
其中firstReader用来记录第一个获取到读锁的线程,firstReaderHoldCount则记录第一个获取到读锁的线程获取读锁的可重入次数。

cachedHoldCounter用来记录最后一个获取读锁的线程获取读锁的可重入次数。

在这里插入图片描述
readHolds是ThreadLocal变量,用来存放除去第一个获取读锁线程外的其他线程获取读锁的可重入次数。ThreadLocalHoldCounter继承了ThreadLocal,因而initialValue方法返回一个HoldCounter对象。

在这里插入图片描述

写锁的获取与释放

在ReentrantReadWriteLock中写锁使用WriteLock来实现。

void lock()

写锁是个独占锁,某时只有一个线程可以获取该锁。 如果当前没有线程获取到读锁和写锁,则当前线程可以获取到写锁然后返回。如果当前已经有线程获取到读锁和写锁,则当前请求写锁的线程会被阻塞挂起。

另外,写锁是可重入锁,如果当前线程已经获取了该锁,再次获取只是简单地把可重入次数加1后直接返回。

在这里插入图片描述
如以上代码所示,在lock()内部调用了AQS的acquire方法,其中tryAcquire是ReentrantReadWriteLock内部的sync类重写的。

在这里插入图片描述
在代码(1)中,如果当前AQS状态值不为0则说明当前已经有线程获取到了读锁或者写锁。

在代码(2)中,如果w==0说明状态值的低16位为0,而AQS状态值不为0,则说明高16位不为0,这暗示已经有线程获取了读锁,所以直接返回false。

而如果w!=0则说明当前已经有线程获取了该写锁,再看当前线程是不是该锁的持有者,如果不是则返回false。

执行到代码(3)说明当前线程之前已经获取到了该锁,所以判断该线程的可重入次数是不是超过了最大值,是则抛出异常,否则执行代码(4)增加当前线程的可重入次数,然后返回true.如果AQS的状态值等于0则说明目前没有线程获取到读锁和写锁,所以执行代码(5)。

其中,对于writerShouldBlock方法,非公平锁的实现为。
在这里插入图片描述
如果代码对于非公平锁来说总是返回false,则说明代码(5)抢占式执行CAS尝试获取写锁,获取成功则设置当前锁的持有者为当前线程并返回true,否则返回false。

公平锁的实现为

在这里插入图片描述
这里还是使用hasQueuedPredecessors来判断当前线程节点是否有前驱节点,如果有则当前线程放弃获取写锁的权限,直接返回false。

void locklnterruptibly()

类似于lock()方法,它的不同之处在于,它会对中断进行响应,也就是当其他线程调用了该线程的interrupt()方法中断了当前线程时,当前线程会抛出异常InterruptedException异常。

在这里插入图片描述

boolean tryLock()

尝试获取写锁,如果当前没有其他线程持有写锁或者读锁,则当前线程获取写锁会成功,然后返回true。

如果当前已经有其他线程持有写锁或者读锁则该方法直接返回false,且当前线程并不会被阻塞。如果当前线程已经持有了该写锁则简单增加AQS的状态值后直接返回true。

在这里插入图片描述

boolean tryLock(long timeout,TimeUnit unit)

与tryAcquire()的不同之处在于,多了超时时间参数,如果尝试获取写锁失败则会把当前线程挂起指定时间,待超时时间到后当前线程被激活,如果还是没有获取到写锁则返回false。

另外,该方法会对中断进行响应,也就是当其他线程调用了该线程的interrupt)方法中断了当前线程时,当前线程会抛出InterruptedException异常。

在这里插入图片描述

void unlock()

尝试释放锁,如果当前线程持有该锁,调用该方法会让该线程对该线程持有的AQS状态值减1,如果减去1后当前状态值为0则当前线程会释放该锁,否则仅仅减1而已。

如果当前线程没有持有该锁而调用了该方法则会抛出IlegalMonitorStateException异常,代码如下。

在这里插入图片描述

读锁的获取与释放

ReentrantReadWriteLock中的读锁是使用ReadLock来实现的。

void lock()

获取读锁,如果当前没有其他线程持有写锁,则当前线程可以获取读锁,AQS的状态值state的高16位的值会增加1,然后方法返回。否则如果其他一个线程持有写锁,则当前线程会被阻塞。
在这里插入图片描述
读锁的lock方法调用了AQS的acquireShared方法,在其内部调用了ReentrantReadWriteLock中的sync重写的tryAcquireShared方法。

在这里插入图片描述
如上代码首先获取了当前AQS的状态值,然后代码(2)查看是否有其他线程获取到了写锁,如果是则直接返回-1,而后调用AQS的doAcquireShared方法把当前线程放入AQS阻塞队列。

如果当前要获取读锁的线程已经持有了写锁,则也可以获取读锁。

但是需要注意,当一个线程先获取了写锁,然后获取了读锁处理事情完毕后,要记得把读锁和写锁都释放掉,不能只释放写锁。

否则执行代码(3),得到获取到的读锁的个数,到这里说明目前没有线程获取到写锁,但是可能有线程持有读锁,然后执行代码(4)。

其中非公平锁的readerShouldBlock实现代码如下。

在这里插入图片描述
如上代码的作用是,如果队列里面存在一个元素,则判断第一个元素是不是正在尝试获取写锁,如果不是,则当前线程判断当前获取读锁的线程是否达到了最大值。

最后执行CAS操作将AQS状态值的高16位值增加1。

代码(5)(6)记录第一个获取读锁的线程并统计该线程获取读锁的可重入数。

代码(7)使用cachedHoldCounter记录最后一个获取到读锁的线程和该线程获取读锁的可重入数,readHolds记录了当前线程获取读锁的可重入数。

如果readerShouldBlock返回true则说明有线程正在获取写锁,所以执行代码(8)。

fullTryAcquireShared的代码与tryAcquireShared类似,它们的不同之处在于,前者通过循环自旋获取。

void locklnterruptibly()

类似于lock()方法,不同之处在于,该方法会对中断进行响应,也就是当其他线程调用了该线程的interrupt()方法中断了当前线程时,当前线程会抛出InterruptedException异常。

boolean tryLock()

尝试获取读锁,如果当前没有其他线程持有写锁,则当前线程获取读锁会成功,然后返回true。

如果当前已经有其他线程持有写锁则该方法直接返回false,但当前线程并不会被阻塞。

如果当前线程已经持有了该读锁则简单增加AQS的状态值高16位后直接返回true。

boolean tryLock(long timeout,TimeUnit unit)

与tryLock()的不同之处在于,多了超时时间参数,如果尝试获取读锁失败则会把当前线程挂起指定时间,待超时时间到后当前线程被激活,如果此时还没有获取到读锁则返回false。

另外,该方法对中断响应,也就是当其他线程调用了该线程的interrupt()方法中断了当前线程时,当前线程会抛出InterruptedException异常。

void unlock()

在这里插入图片描述

如上代码具体释放锁的操作是委托给Sync类来做的,sync.releaseShared方法的代码如下:
在这里插入图片描述
在这里插入图片描述
如以上代码所示,在无限循环里面,首先获取当前AQS状态值并将其保存到变量c,然后变量c被减去一个读计数单位后使用CAS操作更新AQS状态值,如果更新成功则查看当前AQS状态值是否为0,为0则说明当前已经没有读线程占用读锁,则tryReleaseShared返回true。

然后会调用doReleaseShared方法释放一个由于获取写锁而被阻塞的线程,如果当前AQS状态值不为0,则说明当前还有其他线程持有了读锁,所以tryReleaseShared返回false。

如果tryReleaseShared中的CAS更新AQS状态值失败,则自旋重试直到成功。

在这里插入图片描述

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

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

相关文章

CSS 发光输入框动画

<template><view class="content"><input placeholder="请输入..." class="input" /> </view> </template><script></script><style>/* 设置整个页面的背景颜色为 #212121 */body{background-c…

Image - 体积最小的 base64 encode 1*1透明图片,透明背景图片base64编码

背景 前端开发时&#xff0c;有些<img>标签的src属性的值来源于接口&#xff0c;在接口获取结果之前&#xff0c;这个src应该设置为什么呢&#xff1f; 误区&#xff1a;设置为# 有人把src设置为<img src"#" />。 这是有问题的&#xff0c;浏览器解析…

Hyperledger Fabric 权限策略和访问控制

访问控制是区块链网络十分重要的功能&#xff0c;负责控制某个身份在某个场景下是否允许采取某个操作&#xff08;如读写某个资源&#xff09;。 常见的访问控制模型包括强制访问控制&#xff08;Mandatory Access Control&#xff09;、自主访问控制&#xff08;Discretionar…

LabVIEW在旋转机械故障诊断中的随机共振增强应用

在现代工业自动化领域&#xff0c;准确的故障诊断对于保障机械设备的稳定运行至关重要。传统的故障检测方法往往因噪声干扰而难以捕捉到微弱的故障信号。随着LabVIEW在数据处理和系统集成方面的优势日益凸显&#xff0c;其在旋转机械故障诊断中的应用开始发挥重要作用&#xff…

Javaweb之Mybatis的动态SQL的详细解析

3. Mybatis动态SQL 3.1 什么是动态SQL 在页面原型中&#xff0c;列表上方的条件是动态的&#xff0c;是可以不传递的&#xff0c;也可以只传递其中的1个或者2个或者全部。 而在我们刚才编写的SQL语句中&#xff0c;我们会看到&#xff0c;我们将三个条件直接写死了。 如果页面…

APP广告变现技术——广告分层(瀑布流)

广告变现必不可少的广告来源就是AdNetwork&#xff0c;开发者要想让应用在广告变现上获取更多的收益&#xff0c;需要接入更多的ADNetwork&#xff0c;把每一个广告位卖给出价最高的平台&#xff0c;那是如何做到的&#xff1f; 开发者接入多家广告源后&#xff0c;流量应该怎…

实战环境搭建-linux下安装悟空CRM

下载地址如下: 链接:https://pan.baidu.com/s/1OI9EA8Nc8ymWlERS9i0vjg?pwd=ws5c 提取码:ws5c 上传crm的程序包,如下图: 输入 unzip 72crm-java-master.zip 进行解压 create database crm9; use crm9; source /opt/72crm-java-master/docs/crm9.sql 修改/home/wukongcr…

Elasticsearch:升级到 elasticsearch-py 8.x 的 10 个理由

作者&#xff1a;来自 Elastic 公司 Quentin_Pradet 早在 2022 年 2 月&#xff0c;当 Elasticsearch 8.0 发布时&#xff0c;Python 客户端也发布了 8.0 版本。 它是 7.x 客户端的部分重写&#xff0c;并附带了许多不错的功能&#xff08;概述如下&#xff09;&#xff0c;但也…

SpringBoot请求参数加密、响应参数解密

SpringBoot请求参数加密、响应参数解密 1.说明 在项目开发工程中&#xff0c;有的项目可能对参数安全要求比较高&#xff0c;在整个http数据传输的过程中都需要对请求参数、响应参数进行加密&#xff0c;也就是说整个请求响应的过程都是加密处理的&#xff0c;不在浏览器上暴…

Spark---RDD(Key-Value类型转换算子)

文章目录 1.RDD Key-Value类型1.1 partitionBy1.2 reduceByKey1.3 groupByKeyreduceByKey和groupByKey的区别分区间和分区内 1.4 aggregateByKey获取相同key的value的平均值 1.5 foldByKey1.6 combineByKey1.7 sortByKey1.8 join1.9 leftOuterJoin1.10 cogroup 1.RDD Key-Value…

《罗素论教育》笔记

目录 全书架构 书简介 经典摘录 一、教育的理想 教育的基本原理 教育的目的 二、品性的教育 一岁前的教育 主要是2岁到6岁的教育 三、智力教育 14岁前的课程安排 最后的学年 大学教育 四、结束语 全书架构 书简介 经典摘录 一、教育的理想 教育的基本原理 1、我…

vue3+echarts应用——深度遍历html的dom结构并用树图进行可视化

文章目录 ⭐前言&#x1f496;vue3系列文章 ⭐html数据解析&#x1f496; html字符串转为html对象&#x1f496; 深度遍历html对象内容 ⭐echarts 树图的渲染&#x1f496; 处理html内容为树状结构&#x1f496; 渲染树状图&#x1f496; inscode代码块 ⭐总结⭐结束 ⭐前言 大…