Java多线程技术三:锁的使用——使用ReentrantReadWriteLock类

news2024/11/16 1:25:47

1 概述

        ReentrantLock类具有完全互斥排它的特点,同一时间只有一个线程在执行ReentrantLock.lock()方法后面的任务,这样做保证了同时写实例变量的线程安全性,但 效率是非常低下的。在JDK提供了一种读写锁ReentrantReadWriteLock类,可以在同时进行读操作时不需要同步执行,提升运行速度,加快运行效率。这两个类之间没有继承关系。

        读写锁表示有两个锁,一个是读操作相关的锁,也叫共享锁。另一个是写操作相关的锁,也叫排它锁。读锁之间不互斥,读锁和写锁互斥,写锁之间也互斥,说明只要出现写锁,就会出现互斥同步的效果。读操作是指读取实例变量的值,写操作是指向实例变量写入值。

2 ReentrantLock的缺点

        ReentrantLock类与ReentrantReadWriteLock类相比主要的缺点是使用ReentrantLock对象时,所有的操作都同步,哪怕只对实例变量进行读取操作也会同步处理,这样会耗费大量的时间,降低运行效率。

public class MyService {
    private ReentrantLock lock = new ReentrantLock();
    private String username = "abc";

    public void testMethod1(){
        try {
            lock.lock();
            System.out.println("开始执行 testMethod1 方法的线程名 = " + Thread.currentThread().getName() + ";时间是 = " + Utils.data(System.currentTimeMillis()));
            System.out.println("打印 username = " + username);
            Thread.sleep(4000);
            System.out.println("执行完毕 testMethod1 方法的线程名 = " + Thread.currentThread().getName() + ";时间是 = " + Utils.data(System.currentTimeMillis()));
        }catch (InterruptedException e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}
public class ThreadA extends Thread{
    private MyService service;

    public ThreadA(MyService service) {
        this.service = service;
    }
    @Override
    public void run(){
        service.testMethod1();
    }
}
public class Run1 {
    public static void main(String[] args) {
        MyService service = new MyService();
        ThreadA a = new ThreadA(service);
        a.setName("A");
        a.start();
        ThreadA b = new ThreadA(service);
        b.start();
        b.setName("B");
    }
}

944709e435ac4548a629271888451c94.png

        从运行时间来看,2个线程读取实例变量共耗时8秒,每个线程占用4秒,非常浪费CPU资源。而读取实例变量的操作可以是同步进行的,也就是读锁之间可以共享。

3 读锁与读锁之间共享

        修改上面MyService.java代码,在运行Run1.java类。

public class MyService {
    private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    private String username = "abc";
    public void testMethod1(){
        try {
            lock.readLock().lock();
            System.out.println("开始执行 testMethod1 方法的线程名 = " + Thread.currentThread().getName() + ";时间是 = " + Utils.data(System.currentTimeMillis()));
            System.out.println("打印 username = " + username);
            Thread.sleep(4000);
            System.out.println("执行完毕 testMethod1 方法的线程名 = " + Thread.currentThread().getName() + ";时间是 = " + Utils.data(System.currentTimeMillis()));
        }catch (InterruptedException e){
            e.printStackTrace();
        }finally {
            lock.readLock().unlock();
        }
    }
}

 856326abe2064754bb105ba208473f81.png

        从控制台打印的时间来看,两个线程机会同时进入lock方法后面的代码,共耗时4秒,说明使用lock.readLock()方法读锁可以提高程序运行效率,允许多个线程同时执行lock方法后面的代码。

        这个实现中如果不使用锁也可以实现异步运行的效果,那么为什么要使用锁呢?这是因为有可能有第三个线程在执行写操作,这时写操作在执行时,这两个读操作就不能与写操作同时运行了,只能在写操作结束后,这两个读操作才能同时运行,避免了出现非线程安全,而且提高了运行效率。

4 写锁与写锁之间互斥

        再次修改上面的MyService.java,然后运行Run1.java。

public class MyService {
    private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    public void testMethod1(){
        try {
            lock.writeLock().lock();
            System.out.println("获得写锁 方法的线程名 = " + Thread.currentThread().getName() + ";时间是 = " + Utils.data(System.currentTimeMillis()));
            Thread.sleep(10000);
        }catch (InterruptedException e){
            e.printStackTrace();
        }finally {
            lock.writeLock().unlock();
        }
    }
}

 84866316f73d46b38c70b47549f27dc4.png

        使用写锁lock.writeLock()方法的效果就是同一时间只允许一个线程执行lock方法后面的代码。 

5 读写互斥

        

public class MyService {
    private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    public void read(){
        try {
            lock.readLock().lock();
            System.out.println("获得读锁 方法的线程名 = " + Thread.currentThread().getName() + ";时间是 = " + Utils.data(System.currentTimeMillis()));
            Thread.sleep(10000);
        }catch (InterruptedException e){
            e.printStackTrace();
        }finally {
            lock.readLock().unlock();
        }
    }
    public void write(){
        try {
            lock.writeLock().lock();
            System.out.println("获得写锁 方法的线程名 = " + Thread.currentThread().getName() + ";时间是 = " + Utils.data(System.currentTimeMillis()));
            Thread.sleep(10000);
        }catch (InterruptedException e){
            e.printStackTrace();
        }finally {
            lock.writeLock().unlock();
        }
    }
}
public class ThreadA extends Thread{
    private MyService service;

    public ThreadA(MyService service) {
        this.service = service;
    }
    @Override
    public void run(){
        service.read();
    }
}
public class ThreadB extends Thread{
    private MyService service;

    public ThreadB(MyService service) {
        this.service = service;
    }
    @Override
    public void run(){
        service.write();
    }
}
public class Run1 {
    public static void main(String[] args) throws InterruptedException {
        MyService service = new MyService();
        ThreadA a = new ThreadA(service);
        a.setName("A");
        a.start();
        Thread.sleep(1000);
        ThreadB b = new ThreadB(service);
        b.start();
        b.setName("B");
    }
}

195bc3bdf3384af0adbdbabf3de3e45c.png

        这个实验说明读写操作时互斥的,只要出现写操作的过程,就是互斥的。 

6 写锁与读锁互斥

        修改上面的Run1.java类。

public class Run1 {
    public static void main(String[] args) throws InterruptedException {
        MyService service = new MyService();
        ThreadB b = new ThreadB(service);
        b.setName("B");
        b.start();
        Thread.sleep(1000);
        ThreadA a = new ThreadA(service);
        a.setName("A");
        a.start();
    }
}

 929e7d9ccdf84fcaa18c247ccbba5af1.png

        从控制台打印的结果看,写锁与读锁也是互斥的。

【总结】读写、写读、写写之间的操作都是互斥的,只有读读时异步非互斥的。 

 

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

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

相关文章

Android开发中报错总结之一

在我们开发中经常会遇到报错,今天主要是记录一下,我在开发中遇到的问题: 问题一:material-1.5.0-alpha03\res\values-v31\values-v31.xml:3:5-94: AAPT: error: resource android:color/system_neutral1_1000 not found 解决方案…

基于“Galera+MariaDB”搭建多主数据库集群的实例

1、什么是多主数据库集群 多主数据库集群是一种数据库集群架构,每个节点都可以接收写入操作和读取操作,并且通过心跳机制同步数据,保证数据一致性和高可用性。因多主数据库集群每个节点都可以承担读写操作,因此它可以充分利用各个…

HarmonyOS应用开发-手写板(二)

在前一篇手写板的文章中(HarmonyOS应用开发-手写板-CSDN博客),我们通过使用Path实现了一个基本的手写板,但遗憾的是,无法保存所绘制的图像。在本文中,我们将采用canvas和Path2D来重新构建手写板应用。依然只…

数据可视化---柱状图

类别内容导航机器学习机器学习算法应用场景与评价指标机器学习算法—分类机器学习算法—回归机器学习算法—聚类机器学习算法—异常检测机器学习算法—时间序列数据可视化数据可视化—折线图数据可视化—箱线图数据可视化—柱状图数据可视化—饼图、环形图、雷达图统计学检验箱…

PHPStorm一站式配置

phpstorm安装好之后,先别急着编码。工欲善其事,必先利其器,配置好下面这些之后让编码事半功倍。 主题 Appearance & Behavior -> Appearance -> Theme 选中 [Light with Light Header] 亮色较为护眼 关闭更新 Appearance & …

在本地通过 k8s 部署一个 nginx 镜像

目标 目标:通过 deployment 启动一个 nginx,并且通过浏览器访问。 目的,熟悉并学习一下 k8s 的一些特性,毕竟看文档和实操是两码事。 本地部署 k8s 简单点,也不用 minikube 和 kubeadmin,直接通过 docker desktop 部署 k8s。 下载 docker desktop 下载完成后会自动…

Docker的安装及使用

目录 安装Docker 安装yum工具 更新本地镜像源 安装docker 启动docker 关闭防火墙 docker启动命令 配置镜像加速 docker的使用 拉取nginx 查看本地镜像 把镜像文件nginx导出成tar文件 查看是否导出成功 ​编辑 删除本地镜像nginx:latest 导入镜像文件nginx 拉取…

springCould中的Eureka-从小白开始【2】

目录 1.什么是Eureka ❤️❤️❤️ 2. 组件❤️❤️❤️ 3.单机Eureka配置❤️❤️❤️ 4.服务8001服务入住eureka ❤️❤️❤️ 5.消费端80入住到eureka ❤️❤️❤️ 6.集群Eureka配置 ❤️❤️❤️ 7.将Client发布到eureka集群上 ❤️❤️❤️ 8.服务端8002集群搭建…

OpenHarmony 启动流程优化

目前rk3568的开机时间有21s,统计的是关机后从按下 power 按键到显示锁屏的时间,当对openharmony的系统进行了裁剪子系统,系统app,禁用部分服务后发现开机时间仅仅提高到了20.94s 优化微乎其微。在对init进程的log进行分析并解决其…

uniapp 导入ucharts图表插件 H5项目, 使用echarts eopts配置

先下载ucharts H5示例源码: uCharts: 高性能跨平台图表库,支持H5、APP、小程序(微信小程序、支付宝小程序、钉钉小程序、百度小程序、头条小程序、QQ小程序、快手小程序、360小程序)、Vue、Taro等更多支持canvas的框架平台&#…

MyBatis的运行原理!!!

MyBatis框架在操作数据库时,大体经过了8个步骤: 1.读取 MyBatis 配置文件:mybatis-config.xml 为 MyBatis 的全局配置文件,配置了 MyBatis 的运行环境等信息,例如数据库连接信息。 2.加载映射文件:映射文…

如何快速优化大数据量订单表

场景 本篇分享以前在广州一家互联网公司工作时遇到的状况及解决方案,这家公司有一个项目是SOA的架构,这个架构那几年是很流行的,哪怕是现在依然认为这个理念在当时比较先进。 当时的项目背景大概是这样,这家公司用的是某软提供的方案,项目已经运行3年多,整体稳定。 数据…

Pycharm中将画出的图以弹窗方式显示

Pycharm中将画出的图以弹窗方式显示 操作方法: File→ Setting → Tools → 取消Python Scientific 即可。 如下图所示。 然后就搞定了,结果如下。

Ansible常用模块详解(附各模块应用实例和Ansible环境安装部署)

目录 一、ansible概述 1、简介 2、Ansible主要功能: 3、Ansible的另一个特点:所有模块都是幂等性 4、Ansible的优点: 5、Ansible的四大组件: 二、ansible环境部署: 1、环境: 2、安装ansible&#…

智能优化算法应用:基于学校优化算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用:基于学校优化算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用:基于学校优化算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.学校优化算法4.实验参数设定5.算法结果6.…

学习——html基础

什么是HTML Hyper Text Markup Language (超文本标记语言) 标记又俗称标签(tag)&#xff0c;一般格式&#xff1a; 如 <h1></h1>标签里还可以有属性(Attribute)&#xff1a; <tagName Atrribute “value" /> 如 <meta charset"utf-8"…

长短期记忆(LSTM)神经网络-多输入回归预测

目录 一、程序及算法内容介绍&#xff1a; 基本内容&#xff1a; 亮点与优势&#xff1a; 二、实际运行效果&#xff1a; 三、部分程序&#xff1a; 四、完整代码数据下载&#xff1a; 一、程序及算法内容介绍&#xff1a; 基本内容&#xff1a; 本代码基于Matlab平台编…

使用Httpclient来替代客户端的jsonp跨域解决方案

最近接手一个项目&#xff0c;新项目需要调用老项目的接口&#xff0c;但是老项目和新项目不再同一个域名下&#xff0c;所以必须进行跨域调用了&#xff0c;但是老项目又不能进行任何修改&#xff0c;所以jsonp也无法解决了&#xff0c;于是想到了使用了Httpclient来进行服务端…

测试工具Jmeter:界面介绍、核心选项说明、核心选项用途

本文章主要介绍Jmeter的界面布局&#xff0c;以及各个选项的功能和它们的用途。 JMeter基本原理是建立一个线程池&#xff0c;多线程运行取样器产生大量负载&#xff0c;在运行过程中通过断言来验证结果的正确性&#xff0c;通过监听器来记录测试结果。 1. Jmeter主界面 当我…

Linux 进程通信

文章目录 匿名管道匿名管道使用匿名管道原理匿名管道读写 命名管道命名管道使用命名管道特性 共享内存共享内存原理共享内存使用 补充说明 补充说明部分为相关函数和不太重要的概念介绍 匿名管道 匿名管道使用 使用方法一&#xff1a; 使用函数介绍&#xff1a; #include &…