Java内置锁:深度解析ReentrantReadWriteLock并发类

news2024/11/17 3:48:50

Java内置锁:深度解析ReentrantReadWriteLock并发类 - 程序员古德

ReentrantLock和ReentrantReadWriteLock是Java中用于线程同步的重要工具。ReentrantLock提供独占访问,适合需要保护共享资源不被并发修改的场景,同时支持可重入性,适用于递归操作。而ReentrantReadWriteLock则通过读写分离,允许多个线程同时读取资源,但仅允许一个线程写入,从而提高了并发性能。这种锁机制在处理大量读操作和较少写操作的场景中尤为有效。

定义

Java内置锁:深度解析ReentrantReadWriteLock并发类 - 程序员古德

假如,有一个图书馆,图书馆每天都有读者前来借阅书籍,同时也有图书管理员在不断的更新和整理书架上的书籍。

在这个场景中,读者们就好比是线程中的读操作,他们只希望能够安静地阅读书籍,而不需要对书籍进行任何修改。图书管理员则好比是线程中的写操作,他们需要对书架上的书籍进行增加、删除或整理。为了保证图书馆的正常运营,需要制定一些规则来确保读者和管理员之间不会发生冲突,这就是ReadWriteLock接口发挥作用的地方,如下规则:

  1. 当有读者在阅读书籍时(即读锁被占用时),其他读者也可以同时阅读,因为读操作之间是不会相互干扰的,这就像多个读者可以同时站在不同的书架前阅读书籍一样。
  2. 当图书管理员需要整理书架时(即写锁被请求时),必须确保没有读者正在阅读那个书架上的书籍,否则整理过程中可能会导致读者找不到他们正在阅读的书籍,因此,写锁是独占的,一旦被图书管理员占用,其他读者和管理员都必须等待。
  3. 同时,当图书管理员正在整理书架时,其他管理员也不能同时进行整理工作,以免发生混乱。

通过ReadWriteLock接口,可以灵活地控制读操作和写操作之间的并发访问,从而提高程序的性能和响应能力。在这个图书馆的例子中,确保了读者能够高效地阅读书籍,同时管理员也能够安全地进行书架的整理工作。

代码案例

Java内置锁:深度解析ReentrantReadWriteLock并发类 - 程序员古德

假设,有一个共享的数据结构,一个存储用户信息的Map,这个Map会被多个线程同时访问,有的线程需要读取用户信息(读操作),而有的线程需要更新用户信息(写操作),为了保证数据的一致性和并发性能,可以使用ReadWriteLock来管理对Map的访问,如下代码案例:

import java.util.HashMap;  
import java.util.Map;  
import java.util.concurrent.locks.ReadWriteLock;  
import java.util.concurrent.locks.ReentrantReadWriteLock;  
  
public class UserStorage {  
    // 存储用户信息的Map  
    private final Map<String, String> userInfoMap = new HashMap<>();  
    // 读写锁  
    private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();  
  
    // 获取用户信息(读操作)  
    public String getUserInfo(String userId) {  
        // 获取读锁  
        readWriteLock.readLock().lock();  
        try {  
            // 从Map中读取用户信息  
            return userInfoMap.get(userId);  
        } finally {  
            // 释放读锁  
            readWriteLock.readLock().unlock();  
        }  
    }  
  
    // 更新用户信息(写操作)  
    public void updateUserInfo(String userId, String userInfo) {  
        // 获取写锁  
        readWriteLock.writeLock().lock();  
        try {  
            // 更新Map中的用户信息  
            userInfoMap.put(userId, userInfo);  
        } finally {  
            // 释放写锁  
            readWriteLock.writeLock().unlock();  
        }  
    }  
}  
  
public class Client {  
    public static void main(String[] args) {  
        // 创建UserStorage实例  
        UserStorage userStorage = new UserStorage();  
  
        // 模拟多个线程同时访问UserStorage  
        Runnable readTask = () -> {  
            for (int i = 0; i < 5; i++) {  
                String userId = "user" + i;  
                String userInfo = userStorage.getUserInfo(userId);  
                System.out.println("Read Thread: " + Thread.currentThread().getName() + ", User Info: " + userInfo);  
                try {  
                    Thread.sleep(100);  
                } catch (InterruptedException e) {  
                    e.printStackTrace();  
                }  
            }  
        };  
  
        Runnable writeTask = () -> {  
            for (int i = 0; i < 5; i++) {  
                String userId = "user" + i;  
                String userInfo = "Info" + i;  
                userStorage.updateUserInfo(userId, userInfo);  
                System.out.println("Write Thread: " + Thread.currentThread().getName() + ", Updated User Info: " + userInfo);  
                try {  
                    Thread.sleep(200);  
                } catch (InterruptedException e) {  
                    e.printStackTrace();  
                }  
            }  
        };  
  
        // 启动读线程和写线程  
        Thread readThread1 = new Thread(readTask, "ReadThread-1");  
        Thread readThread2 = new Thread(readTask, "ReadThread-2");  
        Thread writeThread = new Thread(writeTask, "WriteThread");  
  
        readThread1.start();  
        readThread2.start();  
        writeThread.start();  
    }  
}

UserStorage类包含一个Map用于存储用户信息,以及一个ReadWriteLock用于控制对Map的并发访问,getUserInfo方法用于获取用户信息,它首先获取读锁,然后从Map中读取用户信息,最后释放读锁,updateUserInfo方法用于更新用户信息,它首先获取写锁,然后更新Map中的用户信息,最后释放写锁,Client类包含一个main方法,用于模拟多个线程同时访问UserStorage,这里创建了两个读线程和一个写线程,它们分别执行读任务和写任务。

运行结果如下,由于线程调度的不确定性,每次运行的结果可能会有所不同,但大致上,会看到读线程和写线程交替执行,读线程可以同时执行,而写线程在执行时会阻止其他线程获取写锁或读锁。

Write Thread: WriteThread, Updated User Info: Info0  
Read Thread: ReadThread-1, User Info: Info0  
Read Thread: ReadThread-2, User Info: Info0  
Write Thread: WriteThread, Updated User Info: Info1  
Read Thread: ReadThread-1, User Info: Info1  
Read Thread: ReadThread-2, User Info: Info1  
...

核心API

ReadWriteLock接口是java.util.concurrent.locks包中的一个重要接口,它定义了读取锁和写入锁的相关操作,ReadWriteLock允许对共享资源进行更高级的并发访问控制,通过分离读操作和写操作来提高并发性能,以下是ReadWriteLock接口中主要方法的作用:

  1. readLock(): 此方法返回一个用于读取操作的锁,多个读取锁可以同时被持有,而不会相互阻塞,但是在持有读取锁的情况下,任何尝试获取写入锁的线程都将被阻塞,直到所有读取锁被释放。
  2. writeLock(): 此方法返回一个用于写入操作的锁,写入锁是独占的,这意味着在给定时间内,只有一个线程能够持有写入锁,当一个线程持有写入锁时,其他任何尝试获取读取锁或写入锁的线程都将被阻塞。

核心总结

Java内置锁:深度解析ReentrantReadWriteLock并发类 - 程序员古德

ReentrantLock和ReentrantReadWriteLock的区别?

1、ReentrantLock

把ReentrantLock想象成一把普通的锁,当一个线程拥有了这把锁,其他线程就不能再获得它,直到拥有锁的线程释放它,这种锁非常适合那些需要独占访问资源的场景,例如,当正在修改一个共享数据结构时,不希望其他线程同时修改它,这时你就可以使用ReentrantLock。此外,ReentrantLock还有一个很酷的特性,那就是可重入性,这意味着同一个线程可以多次获得同一个锁而不会发生死锁,这对于一些需要递归操作的场景非常有用。

2、ReentrantReadWriteLock

与ReentrantLock不同,ReentrantReadWriteLock把锁分为读锁和写锁两种,这是一个非常实用的设计,因为在很多应用场景中,读操作远多于写操作,而且多个读操作之间通常不会相互干扰。使用ReentrantReadWriteLock,多个线程可以同时获得读锁来读取资源,但只有一个线程可以获得写锁来修改资源,并且当有一个线程拥有写锁时,其他线程不能获得读锁或写锁。这种读写分离的设计可以大大提高并发性能,因为读操作不再受到写操作的阻塞。

3、总结

ReentrantLock和ReentrantReadWriteLock都是强大的线程同步工具,如果只需要独占访问资源,那么ReentrantLock是一个不错的选择,但如果应用中有大量的读操作和较少的写操作,并且希望提高并发性能,那么ReentrantReadWriteLock将是更好的选择。

关注我,每天学习互联网编程技术 - 程序员古德

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

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

相关文章

XTuner 微调 课程学习

大语言模型于海量的文本内容上&#xff0c;以无监督和半监督的方式进行训练的 模型微调的目的&#xff1a;使其在具体的使用场景或领域中输出更好的回答 增量预训练——给模型喂新的领域知识&#xff1b; 指令跟随或指令微调—— 基于海量的预训练数据训练出来的模型通常叫做…

[易语言]易语言部署yolox的onnx模型

【官方框架地址】 https://github.com/Megvii-BaseDetection/YOLOX 【算法介绍】 YOLOX是YOLO系列目标检测算法的进一步演变和优化。它由Megvii Technology的研究团队开发&#xff0c;是一个高性能、可扩展的对象检测器。YOLOX在保留快速处理速度的同时&#xff0c;通过引入一…

行业追踪,2024-01-15,含行业对应的etf

自动复盘 2024-01-15 凡所有相&#xff0c;皆是虚妄。若见诸相非相&#xff0c;即见如来。 k 线图是最好的老师&#xff0c;每天持续发布板块的rps排名&#xff0c;追踪板块&#xff0c;板块来开仓&#xff0c;板块去清仓&#xff0c;丢弃自以为是的想法&#xff0c;板块去留让…

Axure RP软件揭秘:设计师的秘密武器

Axure rp是一种快速原型设计工具&#xff0c;可以制作高度互动的HTML原型。设计师不仅可以使用Axure绘制线框图和原型&#xff0c;还可以在Axure rp中完成一系列用户体验设计。在本文中&#xff0c;我们将根据用户体验设计师的真实经验&#xff0c;触发用户体验设计师的实际工作…

Linux之引导和服务篇

系统引导是操作系统运行的开始&#xff0c;在用户能够正常登录之前&#xff0c;Linux的引导过程完成了一系列的初始化任务&#xff0c;并加载必要的程序和命令终端&#xff0c;为用户登录做好准备。 一. 引导过程 开机自检--->MBR引导--->GRUB菜单--->加载Linux内核-…

如何分析测试任务及需求(附分析流程)

测试分析 确认测试范围 根据测试项目的不同需求&#xff0c;有大致几类测试项目类型&#xff1a;商户/平台功能测试、支付方式接入测试、架构调整类测试、后台优化测试、性能测试、基本功能自动化测试。 测试项目需要按照文档要求进行测试需求分析&#xff0c;并给出对应的输出…

Spring MVC文件上传及全局异常处理器

添加依赖 <!--文件上传--> <dependency><groupId>commons-fileupload</groupId><artifactId>commons-fileupload</artifactId><version>1.3.1</version> </dependency>配置文件上传解析器 <!--配置文件上传解析器-…

一个黑盒测试和白盒测试区别的软件测试面试题

软件测试是确保软件质量的重要环节&#xff0c;而在软件测试中&#xff0c;黑盒测试和白盒测试是两种常见的测试方法。跟小一起学习黑盒测试和白盒测试区别&#xff1a; 它们在测试的角度和目标上存在显著区别&#xff0c;本文将深入探讨这两种测试方法的定义、特点、应用场景…

windows平台高dpi介绍

flutter在windows平台如何自定义dpi设置 系统层级的支持(windows平台对高dpi的支持) 主要有两点&#xff1a; 设置系统的缩放比例 (系统及系统自带的app会根据这个设置来进行缩放&#xff1b;自己的app需要结合自己设置的dpi awareness来实现对应的dpi支持)设置进程的dpi aw…

如何利用小程序改变人力资源行业

随着移动互联网的普及和发展&#xff0c;小程序已经成为了人们生活中必不可少的一部分。小程序不仅提供了便捷的服务&#xff0c;还为各行各业提供了创新和发展的空间。在人力资源行业&#xff0c;利用小程序可以为企业和求职者提供更加高效和便捷的招聘和求职服务。下面&#…

第06章_面向对象编程(基础)拓展练习(求三角形面积,猴子吃桃,圆类,学生类,矩形类)

文章目录 第06章_面向对象编程&#xff08;基础&#xff09;拓展练习1、圆类2、学生类3、MyInt类4、MyDate日期类-15、MyDate日期类-26、数学计算工具类7、常识工具类8、学生对象数组9、员工管理类-110、员工管理类-211、比较大小12、数组排序和遍历13、求三角形面积14、图形工…

vue 使用mock模拟数据

vue 使用mock模拟数据 安装依赖 cnpm install axios --save cnpm install mockjs --save-dev cnpm install json5 --save-dev在根目录下&#xff0c;新建一个mock文件&#xff0c;且创建如下文件 utils.js index.js const Mock require(mockjs) const { param2Obj } …

C++ 数组分页,经常有用到分页,索性做一个简单封装 已解决

在项目设计中&#xff0c; 有鼠标滑动需求&#xff0c;但是只能说能力有限&#xff0c;索性使用 php版本的数组分页&#xff0c;解决问题。 经常有用到分页&#xff0c;索性做一个简单封装、 测试用例 QTime curtime QTime::currentTime();nHour curtime.hour();nMin curtim…

PTA-7-4 堆排序

代码如下: #include<iostream> using namespace std; void change(int arr[], int n, int i); int main() {int n,i,end,arr[1000];cin >> n;for (i 0; i < n; i){cin >> arr[i];}//进行一次排序,把最大值放到顶端for (i n/2-1; i > 0; i--){change…

如何去开发直播电商系统小程序

明确你的直播电商系统的功能和特性&#xff0c;包括用户注册、商品展示、购物车、支付结算、直播功能、评论互动等。根据需求确定系统的基本架构和主要模块。 技术选型&#xff1a;选择适合你的直播电商系统的技术栈。考虑前端框架&#xff08;如React、Vue.js&#xff09;、后…

Python中的多种“占位符”和用法解析

更多资料获取 &#x1f4da; 个人网站&#xff1a;ipengtao.com 在Python编程中&#xff0c;经常需要在字符串中插入变量或占位符来构建动态的文本内容。Python提供了多种方式来实现这一目标&#xff0c;本文将介绍常见的“占位符”及其用法&#xff0c;包括字符串格式化、f-s…

开源ERP系统Odoo安装部署并结合内网穿透实现公网访问本地系统

文章目录 前言1. 下载安装Odoo&#xff1a;2. 实现公网访问Odoo本地系统&#xff1a;3. 固定域名访问Odoo本地系统 前言 Odoo是全球流行的开源企业管理套件&#xff0c;是一个一站式全功能ERP及电商平台。 开源性质&#xff1a;Odoo是一个开源的ERP软件&#xff0c;这意味着企…

Elasticsearch添加7.17.10IK分词器

Elasticsearch添加7.17.10IK分词器 在https://github.com/medcl/elasticsearch-analysis-ik/tree/7.x中未找到7.17.10版本的发布版本&#xff0c;如歌ik版本和Elasticsearch版本不同安装后无法启动。所以下载git上的源代码&#xff0c;并手动编译指定版本IK分词器。 &#xff…

跨境电商账号频繁?你的IP可能“不干净”了

疫情促进了跨境电商行业的加速发展&#xff0c;许多卖家也抓住了这波流量红利&#xff0c;跨境电商月入数万&#xff0c;数十万甚至数百万的造福神话也不断在上演&#xff0c;但由于国内外电商运营模式不同&#xff0c;多店运营、用户数据收集、刷单等行为都受到了国外平台的严…

赴美生子月子机构要怎么选?

首先&#xff0c;了解月子机构的背景和信誉度非常重要。查看机构是否合法注册&#xff0c;是否有任何不良记录或投诉。通过与机构的交流和与其他妈妈.的口碑推.荐&#xff0c;了解机构的信誉和口碑。 要选择正规的赴美生子机构。这个很重要哦!正规的机构要有合法的执照&#x…