【JUC】七、读写锁

文章目录

  • 1、读写锁
  • 2、读写锁的体验
  • 3、读写锁的特点
  • 4、锁的演变
  • 5、读写锁的降级
  • 6、复习:悲观锁和乐观锁

1、读写锁

JUC下的锁包的ReadWriteLock接口,以及其实现类ReentrantReadWriteLock

在这里插入图片描述

  • ReadWriteLock 维护了一对相关的锁,即读锁写锁,使得并发和吞吐相比一般的排他锁有了很大提升
  • 读锁属于共享锁
  • 写锁属于独占锁
  • 相比前面的ReentrantLock适用于一般场合,ReadWriteLock 适用于读多写少的场景

关于ReadWriteLock接口的两个方法:

  • 返回用于读的锁
Lock readLock()
  • 返回用于写的锁
Lock writeLock()

2、读写锁的体验

先看没有读写锁时,开多个线程对同一个资源进行读和写:

/资源类
class MyCache{

    //map模拟redis
    private volatile Map<String,Object> map = new HashMap<>();

    //写
    public void put(String key,Object value){
        System.out.println(Thread.currentThread().getName() + "线程正在进行写操作==>" + key);
        //暂停一会儿
        try {
            TimeUnit.MICROSECONDS.sleep(300);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //放数据
        map.put(key,value);
        System.out.println(Thread.currentThread().getName() + "线程写完了==>" + key);
    }

    //取
    public Object get(String key){
        Object result = null;
        System.out.println(Thread.currentThread().getName() + "线程正在进行读操作-->" + key);
        //暂停一会儿
        try {
            TimeUnit.MICROSECONDS.sleep(300);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        result = map.get(key);
        System.out.println(Thread.currentThread().getName() + "线程读完了-->" + key);
        return result;
    }
}

创建5个线程来读,5个线程来写:

public class ReadWriteLockDemo {
    public static void main(String[] args) {
        MyCache myCache = new MyCache();
        //创建5个线程来写数据
        for (int i = 1; i < 6; i++) {
            final int num = i;  //临时变量,直接put一个变量i报错
            new Thread(() -> {
                myCache.put(num +"",num+"");
            },String.valueOf(i)).start();
        }
        //创建5个线程来读数据
        for (int i = 1; i < 6; i++) {
            final int num = i;
            new Thread(() -> {
                myCache.get(num +"");
            },String.valueOf(i)).start();
        }
    }

}

运行发现:没写完就开始读,此时肯定读不到
在这里插入图片描述

加入读锁和写锁:

//资源类
class MyCache{

    //map模拟redis
    private volatile Map<String,Object> map = new HashMap<>();

    //创建读写锁的对象
    private ReadWriteLock rwLock = new ReentrantReadWriteLock();

    //写
    public void put(String key,Object value){
        //添加写锁
        rwLock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "线程正在进行写操作==>" + key);
            //暂停一会儿,别瞬间写完
            TimeUnit.MICROSECONDS.sleep(300);
            //放数据
            map.put(key,value);
            System.out.println(Thread.currentThread().getName() + "线程写完了==>" + key);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            //释放写锁
            rwLock.writeLock().unlock();
        }

    }
	//取
    public Object get(String key){
        //添加读锁
        rwLock.readLock().lock();
        Object result = null;
        try {
            System.out.println(Thread.currentThread().getName() + "线程正在进行读操作-->" + key);
            //暂停一会儿,别读完太快,以证明读锁确实可以共享
            TimeUnit.MICROSECONDS.sleep(300);
            result = map.get(key);
            System.out.println(Thread.currentThread().getName() + "线程读完了-->" + key);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            rwLock.readLock().unlock();
        }
        return result;
    }
}

和上面一样,再执行main:创建5个线程来读,5个线程来写:

在这里插入图片描述

3、读写锁的特点

  • 读读共享:允许多个线程同时对同一个资源进行读,不用等前面的线程释放读锁,后面的线程就能获取到读锁,并执行加了读锁的代码
  • 读写互斥:一个线程获取了读锁,未释放前,不允许另一个线程同时来获取写锁进行写操作
  • 写写互斥:不允许多个线程对同一个资源进行写,必须等到前面线程释放写的锁,后面的线程才能获取到写锁并执行加了写锁的代码

写个demo开两个线程去获取读锁和写锁,调试验证下,这里两个线程都不释放自己获取到的读锁或者写锁:

public class ReadWriteDemo2 {

    public static void main(String[] args) {

        //可重入读写锁
        ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
        //读锁
        ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock();
        //写锁
        ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();

        //获取读锁
        readLock.lock();
        System.out.println("reading....");

        new Thread(() -> {
            //另一线程获取写锁
            writeLock.lock();
            System.out.println("write....");
        }).start();

        //释放写锁
        //writeLock.unlock();

        //释放读锁
        //readLock.unlock();


    }
}

在这里插入图片描述

前面线程先获取写锁,另一线程去获取读锁:

public class ReadWriteDemo2 {

    public static void main(String[] args) {

        //可重入读写锁
        ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
        //读锁
        ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock();
        //写锁
        ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();

        //获取写锁
        writeLock.lock();
        System.out.println("write....");


        new Thread(() -> {
            //另一线程获取读锁
            readLock.lock();
            System.out.println("reading....");
        }).start();

        //释放写锁
        //writeLock.unlock();

        //释放读锁
        //readLock.unlock();


    }
}

在这里插入图片描述

同时获取读锁:

public class ReadWriteDemo2 {

    public static void main(String[] args) {

        //可重入读写锁
        ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
        //读锁
        ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock();
        //写锁
        ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();

        //获取读锁
        readLock.lock();
        System.out.println("reading....");


        new Thread(() -> {
            //另一线程也获取读锁
            readLock.lock();
            System.out.println("reading....");
        }).start();

        //释放写锁
        //writeLock.unlock();

        //释放读锁
        //readLock.unlock();


    }
}

在这里插入图片描述

同时获取写锁:

public class ReadWriteDemo2 {

    public static void main(String[] args) {

        //可重入读写锁
        ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
        //读锁
        ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock();
        //写锁
        ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();

        //获取写锁
        writeLock.lock();
        System.out.println("writing....");


        new Thread(() -> {
            //另一线程也获取读锁
            writeLock.lock();
            System.out.println("writing....");
        }).start();

        //释放写锁
        //writeLock.unlock();

        //释放读锁
        //readLock.unlock();


    }
}


在这里插入图片描述

4、锁的演变

无锁 ⇒ 独占锁 ⇒ 读写锁

在这里插入图片描述

5、读写锁的降级

前面提到,不同线程下,读读共享,读写互斥,写写互斥。

而同一线程中,在持有写锁未解锁的情况下,可以获取读锁。按照如下步骤:

在这里插入图片描述

在同一个线程中,写锁就被过渡降级到了读锁,读写锁的降级,其目的是为了解决,持有写锁时,其他线程无法获得读锁,影响性能。

public class ReadWriteDemo {

    public static void main(String[] args) {

        //可重入读写锁
        ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
        //读锁
        ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock();
        //写锁
        ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();

        //锁降级
        //1.获取写锁
        writeLock.lock();
        System.out.println("write....");

        //2.获取读锁
        readLock.lock();
        System.out.println("reading....");

        //3.释放写锁
        writeLock.unlock();

        //4.释放读锁
        readLock.unlock();


    }
}

如果写锁被释放时,执行读锁的线程非常多,而需要执行写锁的线程非常少,则会导致读锁一直被使用不被释放,从而造成写线程无法获取写锁,造成写线程一直等待获取,造成线程“饥饿”。这个就像某一站地铁,上来100个人,下1个人,结果车一停(类比锁一降级),100个人往进涌(类比其他线程可以获取读锁了),把地铁门堵到发车(好多线程,读锁半天没有全部释放完),导致这一个下车的人也愣是没下去(类比少数其他想获取写锁的线程半天获取不到,因为不同线程,读写互斥)。

补充:

可能会有个疑问,既然释放写锁,干嘛非要手里纂一个读锁后才释放写锁,为何不:

持有写锁 -> 释放写锁 -> 持有读锁 -> 释放读锁

这样的坑在于,你释放完写锁,被另一线程T拿到并写了些数据,等你再拿到读锁时(不是你一释放写锁就一定能给自己拿到读锁,不同线程,读写互斥!),读到的已经是被修改了N手的数据。降级是,你什么时候想要读锁,你就什么时候获取读锁(因为写锁在你手里,你主动的),而如果你先释放写锁,想再获取读锁,那就不是想要就能立马拿到的了。

6、复习:悲观锁和乐观锁

最后,梳理下其他相关的锁。

悲观锁

心态悲观,认为每次操作都会去修改数据,因此,次次操作前都上锁,即共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程。如Java中的synchronized、ReentrantLock就属于悲观锁的范畴(独占锁)。

乐观锁

总是假设好的方向,即认为每次操作都不会修改数据,因此也不上锁,只是在更新时会去判断一下有没人在这期间更新过这个数据,这个"判断",可以使用版本号机制或者CAS算法。版本号即多维护个字段:

# version=version+1
# where xx=#{xx} and version=#{version}
update table set x=x+1, version=version+1 where id=#{id} and version=#{version};  

CAS算法,CAS 即compare and swap,比较与交换,是一种无锁算法,实现了不用锁的情况下进行多线程变量同步,也称非阻塞同步。其实现思路是一种自旋的思想,即不断的重试(这同时也是乐观锁的一个缺点,长时间不成功并重试CPU开销变大)。CAS算法的三个数:

  • 需要读写的内存值 V
  • 进行比较的值 A
  • 拟写入的新值 B

当且仅当 V 的值等于 A时,CAS通过原子方式用新值B来更新V的值,否则不会执行任何操作(比较和替换是一个原子操作)。

ABA问题:

如果一个变量V初次读取的时候是A值,并且在准备赋值的时候检查到它仍然是A值,那我们就能说明它的值没有被其他线程修改过了吗?很明显是不能的,因为在这段时间它的值可能被改为其他值,然后又改回A,那CAS操作就会误认为它从来没有被修改过。这个问题被称为CAS操作的 "ABA"问题。

根据悲观锁与乐观锁的特点,可以知道:

  • 悲观锁适用于多写少读的场景
  • 乐观锁适用于多读少写的场景

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

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

相关文章

TikTok对文化艺术的影响:传统与现代的碰撞

在这个数字时代&#xff0c;社交媒体平台不仅改变了我们的社交方式&#xff0c;也对文化和艺术产生了深远的影响。其中&#xff0c;TikTok是一个备受欢迎的应用&#xff0c;已成为传统与现代文化艺术交汇的独特平台。本文将深入探讨TikTok对文化艺术的影响&#xff0c;以及传统…

Freeswitch中CHANNEL_HOLD保持事件

1.CHANNEL_HOLD保持事件 2023-11-15T09:18:42.6920800 INFO c.e.c.v.s.c.i.FsServerEventHandler - eventReceived:CHANNEL_HOLD 2023-11-15T09:18:42.6920800 INFO c.e.c.v.s.c.i.FsServerEventHandler - EventBody********:{variable_effective_caller_id_number1000, , va…

OpenCV必知必会基础3(包括色彩空间的变换、ROI、OpenCV中最重要的结构体Mat以及获取图像的属性)

文章目录 OpenCV的色彩空间——RGB与BGROpenCV的色彩空间——HSV与HSLHSV主要用于OpenCV中HSL OpenCV色彩空间转换YUV主要用于视频中题目 图像操作的基石Numpy【基础操作】np.arraynp.zerosnp.onesnp.fullnp.identitynp.eye Numpy基本操作之矩阵的检索与赋值Numpy基本操作三——…

Kylin-Server-V10-SP3+Gbase+宝兰德信创环境搭建

目录 一、Kylin-Server-V10-SP3 安装1.官网下载安装包2.创建 VMware ESXi 虚拟机3.加载镜像&#xff0c;安装系统 二、Gbase 安装1.下载 Gbase 安装包2.创建组和用户、设置密码3.创建目录4.解压包5.安装6.创建实例7.登录8.常见问题 三、宝兰德安装1.获取安装包2.解压安装3.启动…

黑马程序员微服务 分布式搜索引擎3

分布式搜索引擎03 0.学习目标 1.数据聚合 **聚合&#xff08;aggregations&#xff09;**可以让我们极其方便的实现对数据的统计、分析、运算。例如&#xff1a; 什么品牌的手机最受欢迎&#xff1f;这些手机的平均价格、最高价格、最低价格&#xff1f;这些手机每月的销售…

【JavaEE初阶】 HTML基础详解

文章目录 &#x1f38b;什么是HTML&#xff1f;&#x1f340;HTML 结构&#x1f6a9;认识标签&#x1f6a9;HTML 文件基本结构&#x1f6a9;快速生成代码框架 &#x1f384;HTML 常见标签&#x1f6a9;注释标签&#x1f6a9;标题标签: h1-h6&#x1f6a9;段落标签: p&#x1f6…

【差旅游记】启程-新疆哈密(2)

哈喽&#xff0c;大家好&#xff0c;我是雷工。 最近需要出差&#xff0c;11月02号第一次去新疆特意记录下去新疆的过程。 01 又过北京西站 本来订的是途径成都中转的路线&#xff0c;结果飞机改点&#xff0c;中转时间太短导致赶不上下班飞机&#xff0c;只好改道北京。 又到…

c# - - - Application.StartupPath(程序安装目录)和Environment.CurrentDirectory(程序工作目录)

Application.StartupPath 应用程序的安装目录&#xff0c;不会改变。 在C:\Users\Administrator\source\repos\ConsoleApp6\bin\Debug目录中&#xff0c;运行ConsoleApp6.exe。 安装目录为&#xff1a;C:\Users\Administrator\source\repos\ConsoleApp6\bin\Debug 在C:\Users…

【文件读取/包含】任意文件读取漏洞 afr_1

1.1漏洞描述 漏洞名称任意文件读取漏洞 afr_1漏洞类型文件读取漏洞等级⭐漏洞环境docker攻击方式 1.2漏洞等级 高危 1.3影响版本 暂无 1.4漏洞复现 1.4.1.基础环境 靶场docker工具BurpSuite 1.4.2.靶场搭建 1.创建docker-compose.yml文件 version: 3.2 services: web: …

EMNLP 2023 | 用于开放域多跳推理的大语言模型的自我提示思想链

©PaperWeekly 原创 作者 | 王金元 单位 | 上海交通大学 研究方向 | 大模型微调及应用 论文标题&#xff1a; Self-prompted Chain-of-Thought on Large Language Models for Open-domain Multi-hop Reasoning 模型&代码地址&#xff1a; https://github.com/noewangj…

idea中git 移除对某个文件的跟踪

应用场景如下 某个log 文件&#xff0c;被同事用git 提交到了服务器&#xff0c;本地拉去之后我们的跟踪也会受影响 取消跟踪的方法如下&#xff1a; 删除本地缓存 git rm --cached "logs/test.log" 提交无效的log git commit -m "ignore log" 再将lo…

三子棋——C语言初阶

一.游戏思路&#xff1a; 设计菜单&#xff0c;选择开始游戏(1)还是退出游戏(0)&#xff08;若是输入数字不再输入范围内&#xff0c;则“选择错误”&#xff09;初始化棋盘打印棋盘&#xff08;步骤二和三不可调换位置&#xff09;玩家下棋&#xff08;坐标落子&#xff09;—…

Java排序算法之堆排序

图解 堆排序是一种常见的排序算法&#xff0c;它借助了堆这种数据结构。堆是一种完全二叉树&#xff0c;它可以分为两种类型&#xff1a;最大堆和最小堆。在最大堆中&#xff0c;每个结点的值都大于等于它的子结点的值&#xff0c;而在最小堆中&#xff0c;每个结点的值都小于等…

力扣第84 题柱状图中最大的矩形 C++ 单调栈 Java

题目 84. 柱状图中最大的矩形 困难 相关标签 栈 数组 单调栈 给定 n 个非负整数&#xff0c;用来表示柱状图中各个柱子的高度。每个柱子彼此相邻&#xff0c;且宽度为 1 。 求在该柱状图中&#xff0c;能够勾勒出来的矩形的最大面积。 示例 1: 输入&#xff1a;heigh…

MySQL(18):MySQL8.0的其它新特性

MySQL从5.7版本直接跳跃发布了8.0版本。 MySQL8.0 新增特征 1.更简便的NoSQL支持。 NoSQL泛指非关系型数据库和数据存储。随着互联网平台的规模飞速发展&#xff0c;传统的关系型数据库已经越来越不能满足需求。从5.6版本开始&#xff0c;MySQL就开始支持简单的NoSQL存储功能…

给女朋友开发个小程序低价点外卖吃还能赚钱

前言 今天又是无聊的一天,逛了下GitHub,发现一个库里面介绍美团饿了吗外卖红包外卖优惠券,先领红包再下单。外卖红包优惠券,cps分成,别人领红包下单,你拿佣金。哇靠,那我岂不是可以省钱还可以赚钱,yyds。。。。想想都美好哈哈哈!!! 回到正题,这个是美团饿了么分销…

基于51单片机电子钟温度计数码显示设计( proteus仿真+程序+设计报告+讲解视频)

这里写目录标题 ✅1.主要功能&#xff1a;✅讲解视频&#xff1a;✅2.仿真设计✅3. 程序代码✅4. 设计报告✅5. 设计资料内容清单&&下载链接✅[资料下载链接&#xff1a;](https://docs.qq.com/doc/DS0Nja3BaQmVtWUpZ) 基于51单片机电子钟温度检测数码显示设计( proteu…

【文件读取/包含】任意文件读取漏洞 afr_3

1.1漏洞描述 漏洞名称任意文件读取漏洞 afr_3漏洞类型文件读取/包含漏洞等级⭐⭐⭐⭐⭐漏洞环境docker攻击方式 1.2漏洞等级 高危 1.3影响版本 暂无 1.4漏洞复现 1.4.1.基础环境 靶场docker工具BurpSuite 1.4.2.环境搭建 1.创建docker-compose.yml文件 version: 3.2 servi…

VSCode配置ESP-IDF

参考其他 文章即可 如果编译时遇到问题&#xff0c;就去找环境变量&#xff0c;多半是环境变量没有配置好。根据自己安装的idf的目录重新配置 环境变量. 如果电脑上有python环境&#xff0c;但是编译时出现找不到python解释器&#xff0c;需要执行下面命令&#xff0c;另外重…

modbus-RTU是一种比较简单、可靠的协议

modbus-RTU是一种比较简单、可靠的协议 RTU, 是modbus中的一种应用层协议&#xff0c;在OSI的第七层 数据格式 应用