JUC 十二. ReentrantReadWriteLock 与 StampedLock

2023-11-13

一. 基础

  1. ReentrantReadWriteLock 可以看为读读共享,读写,写写依然互斥,总结一句话: 读写互斥,读读共享,既一个资源可以被多个读操作或一个写操作访问
  2. ReentrantReadWriteLock 降级策略: 首先同一时间内允许多个读,但是读锁过程中,要获取写锁,当前正持有读锁的线程必须释放,否则无法获取,写锁时,允许插入读锁进来读取
  3. 先总结ReentrantReadWriteLock 的优点: 一. 允许多个读同时执行,在读多写少的需求中效率更高,二.根据该锁的降级策略,在获取写锁后再不释放写锁情况下允许获取读锁,增加数据可见性(也就是获取写锁修改数据成功后,防止写锁不释放,读请求进来不能及时拿到修改完成的数据)
  4. 使用示例
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReentrantReadWriteLockDemo {
    //1.声明读写锁
    private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    //2.共享数据
    private String data = "";

    //3.读方法
    public void read() {
        //1.获取读锁
        readWriteLock.readLock().lock();
        System.out.println("读取数据 data:" + data);
        //2.释放读锁
        readWriteLock.readLock().unlock();
    }

    //4.写方法
    private void write(String value) {
        //1.获取写锁
        readWriteLock.writeLock().lock();
        this.data = value;
        //2.释放写锁
        readWriteLock.writeLock().unlock();
    }

    //运行测试,开启两个线程,一个线程写,一下线程读
    public static void main(String[] args) throws InterruptedException {
        ReentrantReadWriteLockDemo demo = new ReentrantReadWriteLockDemo();
        new Thread(() -> {
            demo.write("aaaa");
        }, "t1").start();

        TimeUnit.SECONDS.sleep(2);

        new Thread(() -> {
            demo.read();
        }, "t1").start();

        TimeUnit.SECONDS.sleep(5);
        System.out.println("主线程最终读取数据 data:" + demo.data);
    }
}

二. ReentrantReadWriteLock 的锁降级

  1. 此处的锁降级是指: 将写锁可以降级为读锁,也就是一个线程通过ReentrantReadWriteLock 获取到了写锁,在没有释放写锁的情况下,还可以获取到读锁
  2. 提供锁降级的原因是: 让线程感知到数据的变化,保证数据可见性
  3. 示例代码,在下方test方法中先获取写锁,然后再不释放写锁的情况下是可以获取读锁的
	public void test(){
		//1.声明读写两把锁
        ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
        ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();
        ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock();
        
        //2.获取写锁
        writeLock.lock();
        System.out.println("获取到写锁");

		//3.在不释放写锁的情况下,获取读锁
        readLock.lock();
        System.out.println("获取到读锁");
        
        //4.释放锁
        writeLock.unlock();
        
        readLock.unlock();
    }
  1. 总结:由ReentrantReadWriteLock 锁降级中可以理解到该锁除了读读共存以外,在不释放写锁的情况下也可以获取读锁继续执行,写读也是共存,但是在读锁没有释放时,无法获取写锁,所以写写互斥,读写也互斥

三. StampedLock 邮戳票据锁

  1. 思考一下 ReentrantReadWriteLock 存在的问题:
  1. 锁饥饿问题,假设当前有99个读请求进来,后续又进来一个写请求,但是需求上要求,只要有修改,马上生效,在使用 ReentrantReadWriteLock,极端情况下可能要等到前面99个读请求执行完毕,这个写请求才会有机会执行,例如一个购物系统,站在商户角度,修改完价格就想马上生效,但是由于高并发前面拍着多个购买的读请求,就会造成这些阻塞购买读请求执行完后,这个修改价格的写请求才能生效
  2. 虽然 ReentrantReadWriteLock 提供了降级策略,获取写锁,在未释放的情况下允许获取读锁,可以提高数据可见性,但是当先获取读锁,在读锁不能释放的情况下不能获取写锁,必须要等到前面的读锁先释放才行
  3. 根据上面两个问题出现了StampedLock 邮戳票据锁
  1. StampedLock 邮戳票据锁是什么: 是java8中根据 ReentrantReadWriteLock 优化升级新增的锁,内部通过一个long类型的stamp标记实现的比ReentrantReadWriteLock 性能更高的锁(stamp表示锁的状态,为0时表示线程获取锁失败,并且释放转换锁类型时需要传递最初获取的stamp的值)
  2. 先说一下 StampedLock 邮戳票据锁的特点:
  1. 在所有调用获取锁方法都会返回一个戳记stamp,如果为0表示获取锁失败,其它表示获取锁成功
  2. 在所有调用释放锁方法都需要传入一个戳记stamp,并且传入的这个戳记要与获取锁时的戳记保持一致
  3. 该锁是不可重入锁,如果写锁已经被获取,再去获取写锁会出现死锁问题
  1. StampedLock 支持三种访问模式
  1. Reading 读模式: 功能与 ReentrantReadWriteLock 类似获取一个读锁
  2. Writing 写模式: 功能与 ReentrantReadWriteLock 类似获取一个写锁
  3. Optimistic reading 乐观读模式: 无锁机制,类似于数据库的乐观锁,支持读写并发,根据stamp判断,如果发现被修改回升级为悲观读
  1. 代码示例(重点关注乐观读,通过stamp验证乐观读锁获取未释放前中间是否有写锁被获取,解决了ReentrantReadWriteLock 中读锁必须被释放才能获取读锁的问题,读写并不会阻塞解决了线饥饿问题,读请求可以马上感知到)
import java.util.concurrent.locks.StampedLock;

public class StampedLockDemo {

    //1.声明StampedLock锁
    private StampedLock stampedLock = new StampedLock();

    //2.共享数据
    private String data = "";

    //写方法
    public void write(String value) {
        //1.获取写锁.返回 stamp 值,如果为0说明获取失败
        long stamp = stampedLock.writeLock();
        try {
            //2.执行业务逻辑
            this.data = value;
        } catch (Exception e) {

        } finally {
            //3.释放写锁,需要传入前面获取锁时返回的stamp戳记,
            stampedLock.unlockWrite(stamp);
        }
    }

    //3.读方法,StampedLock中通过 readLock()获取的读锁与 ReentrantReadWriteLock
    //相同,在当前读锁未释放情况下不可获取写锁
    public void read() {
        //1.获取读锁,返回stamp值,如果为0说明获取锁失败
        long stamp = stampedLock.readLock();
        System.out.println(this.data);
        //2.释放读锁,需要传入获取锁时返回的stamp
        stampedLock.unlockRead(stamp);
    }

    //4.乐观读,通过tryOptimisticRead()获取到读锁时,如果当前读锁未释放
    //允许获取写锁,可能出现数据不安全问题,可以通过stamp作为版本号验证
    //当前读时是否有写锁被获取,如果有被获取防止数据被篡改拿到脏数据
    //在重新读一次数据
    public void tryOptimisticRead() {
        //1.获取乐观读锁,并返回戳记
        long stamp = stampedLock.tryOptimisticRead();

        System.out.println("第一次读取数据 data:" + data);

        //2.通过stamp验证是否有写锁被获取,防止写请求进来修改数据,没有返回true
        boolean flag = stampedLock.validate(stamp);
        if (!flag) {
            //3.当使用乐观读锁,获取读锁未释放时,中间有
            //写锁被获取,当前乐观读锁要切换到普通读锁模式
            //重新获取一个普通读锁,并返回新的戳记stamp
            stamp = stampedLock.readLock();
            System.out.println("有写锁被获取,重新读取 data:" + data);

            //4.释放锁(乐观读锁不需要释放?)
            stampedLock.unlockRead(stamp);
        }
    }

    //运行测试
    public static void main(String[] args) {
        StampedLockDemo demo = new StampedLockDemo();
        //1.写数据
        demo.write("aaaaa");

        //2.读数据
        demo.read();

        //3.乐观读
        demo.tryOptimisticRead();
    }
}
  1. StampedLock邮戳标记锁的缺点: (如果感觉处理不好不推荐使用)
  1. 不支持重入,假设一个线程第一次获取到锁,再次执行该方法,会造成死锁
  2. 普通读锁与普通写锁模式不支持条件变量
  3. 使用该锁时一定不能中断线程操作,例如执行interrupt(),会出现异常
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

JUC 十二. ReentrantReadWriteLock 与 StampedLock 的相关文章

随机推荐

  • Python 快乐数

    快乐数 也不多说它的定义了 直接说相关的概念吧 如下 所有不快乐数的数位平方和计算 最后都会进入 4 16 37 58 89 145 42 20 4 的循环中 已知规律 1 4 中只有 1 是快乐数 5 的数字要么回归到 1 要么回归到 4
  • 奇偶校验位

    在串行通信中 奇偶校验位通常是由UART这样的接口硬件生成 校验的 在接收方 通过接口硬件中的寄存器的状态位传给 CPU 以及操作系统 错误数据的恢复通常是通过重新发送数据 这个过程通常由如操作系统输入输出程序这样的软件处理的
  • (2)Gymnasium--CartPole的测试

    1 主要参考 1 CartPole 强化学习详解1 DQN Oxalate c的博客 CSDN博客 2 官方文档 推荐 Cart Pole Gymnasium Documentation 2 相关说明 2 1 动作空间 取值 0 1 表示推
  • python数组初始化_python怎么初始化数组

    因为画图中x轴与y轴的数据通常为数组格式的数据 所以先总结一下如何初始化数组 1 list得到数组 通过array函数传递list对象 L 1 2 3 4 5 6 a np array L 若传递的是多层嵌套的list 将创建多维数组 b
  • 【爬虫】一、BeautifulSoup库

    文档内容为本人观看北京理工大学嵩天老师公开课的听课笔记与实践总结 图片为从该课程下载资料的截图 感谢嵩老师 Key point 网页内容提取实际上是对标签的内容进行提取 其关键是标签的获取和标签感兴趣内容的提取 获取标签用beautiful
  • win10计算机设备感叹号,win10网络适配器出现感叹号的解决方法

    Win10系统仍然在不断完善 所以用户在使用过程中总会遇到一些陌生的问题 比如 有位用户在新装或重装的Win10系统中 就碰到了网卡不能安装 或安装出错 安装好网卡不能加载等等各种网卡驱动问题 今天小编就为大家简单的介绍一下Win10系统安
  • vtk光照、颜色、相机、坐标系统及空间变换

    1 vtkLight常的方法有 SetColor 设置光照的颜色 以RGB的形式指定颜色 SetPosition 设置光照位置 SetFocalPoint 设置光照焦点 SetIntensity 设置光照的强度 SetSwitch Swit
  • jsrender的基本使用

    1 什么是jsrender 一个JavaScript库 允许您定义一次样板结构并重复使用它来动态生成HTML JsRender为HTML5开发带来了一个新的模板库 它具有无代码标记语法和高性能 不依赖于jQuery 也不依赖于文档对象模型
  • Go-新手速成-流程语句

    1if Go的if不建议写 over if条件判断 age 16 if age lt 18 fmt Println 未成年 2for循环 Go摈弃了while和do while 循环 因为他做到了极简 也不要括号 这么写可以 total 0
  • Pandas知识点-reset_index,reindex,reindex_like,你分得清吗?

    Pandas知识点 reset index reindex reindex like 你分得清吗 reset index 用法详解 reset index 是pandas中将索引重置成自然数的方法 不会改变原始数据的内容和排列顺序 Data
  • 2023年第五届清洁能源与智能电网国际会议(CCESG 2023)

    2023年第五届清洁能源与智能电网国际会议 CCESG 2023 重要信息 会议网址 www ccesg org 会议时间 2023年11月3 5日 召开地点 广西 南宁 截稿时间 2023年10月3日 录用通知 投稿后2周内 收录检索 E
  • Python3基础入门

    文章目录 前言 基础说明 Python安装 Windows Ubuntu 开发环境 程序编写 模块和包 模块 module 包 package pip和换源 总结 前言 Python是目前非常流行的编程语言 这篇文章将对其相关入门内容进行说
  • JS判断数据类型的5种方法

    我们先来了解一下JS中数据类型有哪些 基本数据类型 值类型 String Number boolean null undefined symbol es6新增的 引用数据类型 引用类型 object 包含 Function Array Da
  • CSS line-height概念与举例

    本文同时发表在https github com zhangyachen zhangyachen github io issues 37 定义 两行文字基线之间的距离 基线的大体位置 基线的位置可以看成x字母下边缘的位置 不同字体的基线位置会
  • 微信公众号H5音频视频自动播放(安卓,苹果)

    我们都知道音频视频的自动播放被浏览器或者微信给限制了 必须用户跟页面交互才可以播放音视频 解决办法就是引入微信的jssdk 然后监听 WeixinJSBridgeReady 来实现自动播放 引入jssdk 音频或视频自动播放 documen
  • 查看运行的java程序的几种方式

    windows 任务管理器可以查看进程和线程数 也可以用来杀死进程 tasklist 查看进程 tasklist 杀死进程 linux ps ef 查看所有进程 ps ft p 查看某个进程 PID 的所有线程 kill 杀死进程 top
  • 【转载】手把手教你用 “三步法” 快速实现 4K+ 超高分辨率满细节出图

    手把手教你用 三步法 快速实现 4K 超高分辨率满细节出图 https ngabbs com read php tid 35888357 rand 488 准备工作 如果你的显存不足以直出你期望的最终分辨率 请先按照你习惯的方式安装 切片扩
  • uniapp - Map地图组件属性示例

    目录 1 markers 点标记 用于在地图上显示标记的位置 2 点聚合 3 polygons 4 include points 可以实现自动缩放展示视图内所有的点标记 5 polyline 线 map uni app官网 1 marker
  • littleVGL学习笔记5——lv_obj 基础对象

    1 介绍 littleVGL 是以对象为概念的 而其最核心的基础对象是 lv obj 控件 其他的所有专用控件 比如按钮 标签 列表等 都是在此 lv obj 对象的基础上衍生出来的 所有的控件对象都具有一些共同的属性 如下所示 位置 Po
  • JUC 十二. ReentrantReadWriteLock 与 StampedLock

    目录 一 基础 二 ReentrantReadWriteLock 的锁降级 三 StampedLock 邮戳票据锁 一 基础 ReentrantReadWriteLock 可以看为读读共享 读写 写写依然互斥 总结一句话 读写互斥 读读共享