【JUC】12.读写锁与StampedLock[完结]

news/2024/4/27 18:06:39/文章来源:https://blog.csdn.net/weixin_51146329/article/details/127522878

文章目录

  • 1. 什么是读写锁
  • 2. 锁的演化历程
  • 3. 锁降级
  • 4. 锁降级的策略
  • 5. StampedLock简介
  • 6. StampedLock的特点
  • 7. StampedLock之传统读写
  • 8. StampedLock之乐观锁
  • 9. StampedLock缺点

1. 什么是读写锁

读写锁是指ReentrantReadWriteLock类

该类能被多个读线程访问或者一个写线程访问,但是不能同时存在读写线程

其特点主要是:一体两面、读写互斥、读读共享

有线程在写的时候,其他线程不能读;有线程在读的时候,其他线程不能写


2. 锁的演化历程

第一阶段:无锁

在无锁的阶段,会导致数据大乱,多个线程写入数据,导致出现脏数据

第二阶段:synchronized 与 Lock接口(ReentrantLock类)

这两个的出现,使得线程有序,且能保持数据一致性

无论多少个线程过来,不管读还是写,每次都是一个

每次线程都是只有一个,所以会导致多个线程想读也只能一个个线程读,效率比较慢,因为读之间应该是可以共享的

第三阶段:ReadWriteLock接口(ReentrantReadWriteLock)

这个类不仅可以读写互斥,并实现了读读共享,多个线程并发可以访问,大面积可以容许多个线程来读取

在读多写少的时候,可以使用读写锁

但是也有以下缺点:

  1. 写锁饥饿问题:比如有10W个线程是读,只有一个线程是写的时候,会导致写的线程一直抢占不到资源,出现写锁饥饿问题
  2. 会出现锁降级问题

第四阶段:邮戳锁StampedLock

读的时候也允许写锁的接入(读写两个操作也让你“共享”),这样会导致我们读的数据就可能不一致,所以需要额外的方法来判断写的操作是否有写入,这是一种乐观锁

虽然乐观锁并发效率更高,但是一栏有小概率的写入导致读取的数据不一致,需要能检测出来,再读一次即可


3. 锁降级

什么是锁降级

锁降级就是ReentrantReadWriteLock将写入锁降级为读锁(就像Linux一样,写权限高于读权限),锁的严苛程度变强叫做升级,反之叫做降级

在这里插入图片描述

  1. 如果同一个线程有了写锁,在没有释放写锁的情况下,它还可以继续获取读锁。这就是写锁的降级,降级成了读锁
  2. 规则惯例,先获取写锁,然后获取读锁,再释放写锁的次序
  3. 如果释放了写锁,那么就完全转换为读锁
public class LockDownGradingDemo {public static void main(String[] args) {ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();writeLock.lock();System.out.println("正在写入.....");readLock.lock();System.out.println("正在读.....");writeLock.unlock();readLock.unlock();}
}

在这里插入图片描述

但是注意的是,锁可以降级,但是不可以升级,且写锁和读锁是互斥的

锁降级:遵循获取写锁–》再获取读锁–》再释放写锁的次序,写锁能够降级成为读锁

在这里插入图片描述

锁降级是为了让当前线程感知到数据的变化,目的是保证数据的可见性

当读锁被使用时,如果有线程尝试获取写锁,该写线程会被阻塞。所以需要释放所有读锁,才能获取写锁

写锁和读锁是互斥的(这里的互斥是指线程间的互斥,当前线程可以获取到写钱锁又获取到读锁,但是获取到了读锁不能继续获取写锁),这是因为读写锁要保持写操作的可见性。因为,如果允许读锁在被获取的的情况下对写锁的获取,那么正在运行的其他读线程无法感知到当前写线程的操作。

因此,分析读写锁ReentrantReadWriteLock,会发现它有个潜在的问题:
读锁结束,写锁有望;写锁独占,读写全堵
如果有线程正在读,写线程需要等待读线程释放锁后才能获取写锁

即ReentrantReadWriteLock读的过程中不允许写,只有等待线程都释放了读锁,当前线程才能获取写锁,也就是写入必须等待,这是一种悲观的读锁,o(一_-)o,人家还在读着那,你不先别去写,省的数据乱。


4. 锁降级的策略

为什么要锁降级呢?

在这里插入图片描述

cacheValid是一个布尔值,默认为false,如果其他线程修改过这里的数据,那么会将cacheValid改为true

上面案例是首先先读取一次数据,然后接着获取写锁,获取写锁后,判断cacheValid是否有被修改过,如果没有那将data修改为某个值,然后将cacheValid设置为true。

接着锁降级获取读锁,获取读锁之后释放写锁

对数据进行读操作

这样做的好处有以下几点:

  • 在写锁释放之前锁降级成读锁,防止其他线程竞争
  • 这样使得写完之后立马读,防止了其他线程对data进行修改从而出现脏读

5. StampedLock简介

StampedLock优化的主要是ReentrantReadWriteLock出现的锁饥饿问题

stamp代表的是锁的状态。当stamp返回零时,表示线程获取锁失败。

并且,当释放锁或者转换锁的时候,都要传入最初获取的stamp值

回顾锁饥饿问题

ReentrantReadWriteLock实现读写分离,一旦读操作比较多的时候,想要获取写锁就会变得比较困难了,假如有1000个线程,999个读,1个写,有可能999个读取线程长时间抢到了锁,那么1个写线程就悲剧了,因此当前有可能会一直存在读锁,而无法获得写锁

ReentrantReadWriteLock的读锁被占用的时候,其他线程尝试获取写锁的时候会被阻塞。
但是,StampedLock采取乐观获取锁后,其他线程尝试获取写锁时不会被阳塞,这其实是对读锁的优化,
所以,在获取乐观读锁后,还需要对结果进行校验。


6. StampedLock的特点

  1. 所有获取锁的方法,都返回一个邮戳(Stamp),Stamp为零表示获取失败,其余都表示成功
  2. 所有释放锁的方法,都需要一个邮戳(Stamp),这个Stamp必须是和成功获取锁时得到的Stamp一致
  3. StampedLock是不可重入锁,因为邮戳只有一个,危险(如果一个线程已经持有写锁,再去获取写的话就会导致死锁)
  4. StampedLock有三种访问模式
    1. Reading(读模式悲观),功能和ReentrantReadWriteLock的写锁类似
    2. Writing(写模式),功能和ReentrantReadWriteLock的写锁类似
    3. Optimistic reading(乐观读模式):无锁机制,类似于数据库中的乐观锁,支持读写并发,很乐观认为读取的时候没人修改。假如被修改再实现升级为悲观读模式

7. StampedLock之传统读写

/*** @Author: lrk* @Date: 2022/10/25 下午 8:52* @Description:*/
public class StampedLockDemo {static int number = 37;static StampedLock stampedLock = new StampedLock();public void write() {long stamp = stampedLock.writeLock();System.out.println(Thread.currentThread().getName() + "\t" + "写线程准备修改");try {number += 13;} finally {stampedLock.unlockWrite(stamp);}System.out.println(Thread.currentThread().getName() + "\t" + "写线程准备结束");}//悲观读,都没有完成的时候写锁无法获取锁public void read() {long stamp = stampedLock.readLock();System.out.println(Thread.currentThread().getName() + "\t" + "准备读,请等待.....");for (int i = 0; i < 4; i++) {try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(Thread.currentThread().getName() + "\t" + "正在读取中");}try {int result = number;System.out.println(Thread.currentThread().getName() + "\t" + "获取成员变量值result: " + result);System.out.println("写线程没有修改成功,读锁的时候写锁无法接入,传统读写互斥");} finally {stampedLock.unlockRead(stamp);System.out.println(Thread.currentThread().getName() + "\t" + "读线程准备结束");}}public static void main(String[] args) {StampedLockDemo resource = new StampedLockDemo();new Thread(() -> {resource.read();}, "readThread").start();try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {throw new RuntimeException(e);}new Thread(() -> {resource.write();}, "writeThread").start();}
}

上面案例使用的Reading和Writing两种访问模式,实现的效果与ReentrantReadWriteLock的读写锁效果一样

在这里插入图片描述


8. StampedLock之乐观锁

public class StampedLockDemo {static int number = 37;static StampedLock stampedLock = new StampedLock();public void write() {long stamp = stampedLock.writeLock();System.out.println(Thread.currentThread().getName() + "\t" + "写线程准备修改");try {number += 13;} finally {stampedLock.unlockWrite(stamp);}System.out.println(Thread.currentThread().getName() + "\t" + "写线程准备结束");}public void read() {long stamp = stampedLock.readLock();System.out.println(Thread.currentThread().getName() + "\t" + "准备读,请等待.....");for (int i = 0; i < 4; i++) {try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(Thread.currentThread().getName() + "\t" + "正在读取中");}try {int result = number;System.out.println(Thread.currentThread().getName() + "\t" + "获取成员变量值result: " + result);System.out.println("写线程没有修改成功,读锁的时候写锁无法接入,传统读写互斥");} finally {stampedLock.unlockRead(stamp);System.out.println(Thread.currentThread().getName() + "\t" + "读线程准备结束");}}public void tryOptimisticRead() {long stamp = stampedLock.tryOptimisticRead();int result = number;System.out.println("4s前stampedLock.validate方法值(true无修改,false有修改)" + "\t" + stampedLock.validate(stamp));for (int i = 0; i < 4; i++) {try {TimeUnit.SECONDS.sleep(1);System.out.println(Thread.currentThread().getName() + "\t" + "正在读取...." + i + "秒" +stampedLock.validate(stamp) + "后stampedLock.validate方法值(true无修改,false有修改)" + "\t" + stampedLock.validate(stamp));} catch (InterruptedException e) {throw new RuntimeException(e);}}if (!stampedLock.validate(stamp)) {System.out.println("有人修改过--------有写操作");stamp = stampedLock.readLock();try {System.out.println("从乐观读升级为悲观读");result = number;System.out.println("重新悲观读result: " + result);} finally {stampedLock.unlockRead(stamp);}}System.out.println(Thread.currentThread().getName() + "\t" + "finally value:" + result);}public static void main(String[] args) {StampedLockDemo resource = new StampedLockDemo();new Thread(() -> {resource.tryOptimisticRead();}, "readThread").start();try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {throw new RuntimeException(e);}new Thread(() -> {resource.write();}, "writeThread").start();}private static void extracted(StampedLockDemo resource) {new Thread(() -> {resource.read();}, "readThread").start();try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {throw new RuntimeException(e);}new Thread(() -> {resource.write();}, "writeThread").start();}
}

在这里插入图片描述


9. StampedLock缺点

  1. StampedLock不支持重入,没有Re开头
  2. StampedLock的悲观读锁和写锁都不支持条件变量(Condition),这个也需要注意
  3. 使用StampedLock一定不要调用中断操作,即不要调用interrupt()方法

来源:
尚硅谷2022版JUC并发编程(对标阿里P6-P7)

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

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

相关文章

第二节:数据类型与变量【java】

目录 &#x1f4c3;前言 &#x1f4d7;1.数据类型 &#x1f4d5;2. 变量 2.1 变量概念 2.2 语法格式 &#x1f4d9;3.整型变量 3.1 整型变量 3.2 长整型变量 3.3 短整型变量 3.4 字节型变量 &#x1f4d8;4.浮点型变量 4.1 双精度浮点型 4.2 单精度浮点型 &#…

拓端tecdat|用Python进行图像模糊处理和特征提取

全文链接&#xff1a;http://tecdat.cn/?p9015 原文出处&#xff1a;拓端数据部落公众号 在本文中&#xff0c;我将带您了解图像处理的一些基本功能。特征提取。但是这里我们需要更深入的数据清理。但是数据清理是在数据集&#xff0c;表格&#xff0c;文本等上完成的。如何在…

vue使用canvas实现手写电子签名;vue使用vue-esign插件实现手写电子签名;H5使用画布签名

功能&#xff1a; 1.兼容 PC 和 Mobile&#xff1b; 2.画布自适应屏幕大小变化&#xff08;窗口缩放、屏幕旋转时画布无需重置&#xff0c;自动校正坐标偏移&#xff09;&#xff1b; 3.自定义画布尺寸&#xff08;导出图尺寸&#xff09;&#xff0c;画笔粗细、颜色&#xff0…

Arduino UNO 可视化GT-24工业级无线透传

Arduino UNO 可视化GT-24工业级无线透传一、前言二、硬件要求三、参数基础四、原理剖析五、透传思路六、程序概要七、arduino使用接线八、成果展示一、前言 无线透传市面上较为常见的是基于蓝牙、esp的多种透传模块&#xff0c;今天介绍的则是用NRF24L01芯片构成的电路。&…

Python第七章作业

实例一: class Geese: 大雁类 def __init__(self,beak,wing,claw): print("我是大雁类!我有以下特征:") print(beak) print(wing) print(claw) def fly(self,state): print(state)**********调用方法**********beak_…

MyBatis中的reflection包(一)ObjectFactory,PropertyTokenizer, Invoker, Reflector

内容概要 reflection是MyBatis关于反射的工具包&#xff0c;是实现其它功能的基石之一。这里我不准备贴上源码然而逐行解释&#xff0c;而是从需求分析的角度来复现。 ObjectFactory 现在有这样的需求&#xff1a;给你一个Class对象&#xff0c;要求你创建它的实例&#xff…

Kong自动注册kong-spring-boot-stater

前言 kong-spring-boot-stater框架是为了解决SpringBoot项目和kong网关的自动注册&#xff0c;虽然Kong网关有提供可视化管理后台的操作界面&#xff0c;但是在多服务、多环境的时候在管理后台挨个配置每个服务节点是比较麻烦的&#xff0c;所以这也是kong-spring-boot-stater…

图形写稿基础,含teaser figure的特殊排版方法

写在前面&#xff1a;这是第一次投稿后针对论文写作部分的总结。需要注意的是&#xff1a;老师提了意见&#xff0c;一定要快速改&#xff0c;否则会很恼人。 1. 图片展示 构图要美观&#xff0c;保证横平竖直&#xff1b;图片中文字保证和文章正文中文字一样大小&#xff1b;…

VUE |“ 登录页面”的权限以及接口问题

目录 一、功能需求 二、前提准备 三、具体实现 一、功能需求 今天写到项目的登录页面&#xff0c;我这边是没有后台数据接口的&#xff0c;所以我们用了Mock模拟了一个假的数据&#xff0c;那么我们应该怎么实现呢&#xff1f;我们先来看一下功能需要。 当我们退出登录…

系分 - 系统可靠性分析与设计

个人总结&#xff0c;仅供参考&#xff0c;欢迎加好友一起讨论 系分 - 系统可靠性分析与设计 考点摘要 可靠性相关基本概念&#xff08;★&#xff09;系统可靠性分析&#xff08;★★&#xff09;软件可靠性设计&#xff08;★★&#xff09; 系统故障类型 系统故障是指由…

09代码

实例1 def division():print(\n==========分苹果了===========\n)apple=int(input(请输入苹果的个数))children=int(input(请输入来了多少个小朋友))result=apple//childrenremain=apple-result*childrenif remain>0:print(apple,个苹果,平均分给,children,个小朋友,每人分…

网络爬虫及openyxl模块

网络爬虫及openyxl模块 一、第三方模块简介 1.第三方模块的用处python之所以在这么多的编程语言中脱颖而出的优点是有众多的第三方库函数,可以更高效率的实现开发2.第三方模块的使用 1.第三方模块必须下载才能使用格式:pip install 模块名 -i 源地址清华大学 :https://pypi.…

【cuda编程】CUDA的运行方式以及grid、block结构关系

文章目录1. CUDA基础知识1.1 程序基本运行顺序1.2 grid与block1.3 dim类型定义2. CUDA的第一个程序3. CUDA线程的组织结构——grid与block关系1. CUDA基础知识 1.1 程序基本运行顺序 一般来说&#xff0c;一个cpugpu的程序运行如下所示&#xff1a; 1.2 grid与block 从GPU至…

网络原理——No.4 传输层_TCP协议中的延迟应答, 捎带应答, 面向字节流与TCP的异常处理

JavaEE传送门JavaEE 网络原理——No.2 传输层_TCP的连接管理 网络原理——No.3 传输层_TCP的滑动窗口, 流量控制与拥塞控制 目录延迟应答捎带应答面向字节流粘包问题TCP 中的异常处理(连接异常)TCP 和 UDP 的应用场景延迟应答 一种提高传输效率的机制, 又是基于流量控制, 来引…

调度线程池ScheduledThreadPoolExecutor源码解析

实现机制分析 我们先思考下&#xff0c;如果让大家去实现ScheduledThreadPoolExecutor可以周期性执行任务的功能&#xff0c;需要考虑哪些方面呢&#xff1f; ScheduledThreadPoolExecutor的整体实现思路是什么呢&#xff1f; 答&#xff1a; 我们是不是可以继承线程池类&am…

docker快速安装redis

一.背景 开发环境中&#xff0c;经常需要redis本地环境&#xff0c;方便开发。准备在本机的虚拟机里面准备一个redis环境。 二.版本信息 操作系统&#xff1a;Windows 10 家庭版 Oracle VM VirtualBox&#xff1a;版本 6.0.10 r132072 (Qt5.6.2) Ubuntu:16.04.6-desktop-a…

STM32CubeMX学习笔记(44)——USB接口使用(HID按键)

一、USB简介 USB&#xff08;Universal Serial BUS&#xff09;通用串行总线&#xff0c;是一个外部总线标准&#xff0c;用于规范电脑与外部设备的连接和通讯。是应用在 PC 领域的接口技术。USB 接口支持设备的即插即用和热插拔功能。USB 是在 1994 年底由英特尔、康柏、IBM、…

淘宝十年技术思考与总结,让人惊叹的进化脱变,最终确认版已发布

看了淘宝在将近10年时间里技术的革新&#xff0c;我对技术与业务有了更近一步的认识。 任何技术都是从小做起&#xff0c;一步步做起来的。如果你让04年的淘宝去做一个能承受10亿次访问的网站&#xff0c;马云那时候肯定会伤透脑筋&#xff0c;即使做半年都做不出来。但现在&a…

Java实现邮件发送

这里我们以QQ邮箱为例。 一、导入依赖:<dependencies><!-- https://mvnrepository.com/artifact/javax.activation/activation --><dependency><groupId>javax.activation</groupId><artifactId>activation</artifactId><versio…

联邦学习:联邦异构知识图谱划分

在联邦场景下,C个知识图谱位于不同的客户端上。知识图谱拥的实体集合之间可能会存在重叠,而其关系集合和元组集合之间则不会重叠。我们联系一下现实场景看这是合理的,比如在不同客户端对应不同银行的情况下,由于不同银行都有着自己的业务流程,所以关系集合不重叠。本文我们…