多线程系列(十) -ReadWriteLock用法详解
一、摘要
在上篇文章中,我们讲到ReentrantLock可以保证了只有一个线程能执行加锁的代码。
但是有些时候,这种保护显的有点过头,比如下面这个方法,它仅仅就是只读取数据,不修改数据,它实际上允许多个线程同时调用的。
public class Counter {
private final Lock lock = new ReentrantLock();
private int count;
public int get() {
// 加锁
lock.lock();
try {
return count;
} finally {
// 释放锁
lock.unlock();
}
}
}
站在程序性能的角度,实际上我们想要的是这样的效果。
- 1.读和读之间不互斥,因为只读操作不会有数据安全问题
- 2.写和写之间互斥,避免一个写操作影响另外一个写操作,引发数据计算错误问题
- 3.读和写之间互斥,避免读操作的时候写操作修改了内容,引发数据脏读问题
总结起来就是,允许多个线程同时读,但只要有一个线程在写,其他线程就必须排队等待。
在 JDK 中有一个读写锁ReadWriteLock,使用它就可以解决这个问题,它可以保证以下两点:
- 1.只允许一个线程写入,其他线程既不能写入也不能读取
- 2.没有写入时,多个线程允许同时读,可以提高程序并发性能
实际上,读写锁ReadWriteLock里面有两个锁实现,一个是读操作相关的锁,称为共享锁,当多个线程同时操作时,不会让多个线程进行排队等待,大大的提升了程序并发读的执行效率;另一个是写操作相关的锁,称为排他锁,当多个线程同时操作时,只允许一个线程写入,其他线程进入排队等待;两者进行组合操作,就可以实现上面的预期效果。
下面我们一起来看看它的基本用法!
二、ReadWriteLock 基本用法
2.1、读和读共享
读和读之间不互斥,当多个线程进行读的时候,不会让多个线程进行排队等待。
我们可以看一个简单的例子!
public class Counter {
private final ReadWriteLock lock = new ReentrantReadWriteLock();
private int count;
public void read() {
// 加读锁
lock.readLock().lock();
try {
String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS").format(new Date());
System.out.println(time + " 当前线程:" + Thread.currentThread().getName() + "获得了读锁,count:" + count);
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 释放读锁
lock.readLock().unlock();
}
}
}
public class MyThreadTest {
public static void main(String[] args) {
Counter counter = new Counter();
Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
counter.read();
}
});
Thread threadB = new Thread(new Runnable() {
@Override
public void run() {
counter.read();
}
});
threadA.start();
threadB.start();
}
}
看一下运行结果:
2023-10-23 16:12:28:119 当前线程:Thread-0获得了读锁,count:0
2023-10-23 16:12:28:119 当前线程:Thread-1获得了读锁,count:0
从日志时间上可以很清晰的看到,尽管加锁了,并且休眠了 5 秒,但是两个线程还是几乎同时执行try()方法里面的代码,证明了读和读之间是不互斥的,可以显著提高程序的运行效率。
2.2、写和写之间互斥
写和写之间互斥,当多个线程进行写的时候,只允许一个线程写入,其他线程进入排队等待。
我们可以看一个简单的例子!
public class Counter {
private final ReadWriteLock lock = new ReentrantReadWriteLock();
private int count;
public void write() {
// 加写锁
lock.writeLock().lock();
try {
count++;
String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS").format(new Date());
System.out.println(time + " 当前线程:" + Thread.currentThread().getName() + "获得了写锁,count:" + count);
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 释放写锁
lock.writeLock().unlock();
}
}
}
public class MyThreadTest {
public static void main(String[] args) {
Counter counter = new Counter();
Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
counter.write();
}
});
Thread threadB = new Thread(new Runnable() {
@Override
public void run() {
counter.write();
}
});
threadA.start();
threadB.start();
}
}
看一下运行结果:
2023-10-23 16:29:59:103 当前线程:Thread-0获得了写锁,count:1
2023-10-23 16:30:04:108 当前线程:Thread-1获得了写锁,count:2
从日志时间上可以很清晰的看到,两个线程进行串行执行,证明了写和写之间是互斥的。
2.3、读和写之间互斥
读和写之间互斥,当多个线程交替进行读写的时候,操作上互斥,只有一个线程能进入,其他线程进入排队等待。
我们可以看一个简单的例子!
public class Counter {
private final ReadWriteLock lock = new ReentrantReadWriteLock();
private int count;
public void read() {
// 加读锁
lock.readLock().lock();
try {
String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS").format(new Date());
System.out.println(time + " 当前线程:" + Thread.currentThread().getName() + "获得了读锁,count:" + count);
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 释放读锁
lock.readLock().unlock();
}
}
public void write() {
// 加写锁
lock.writeLock().lock();
try {
count++;
String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS").format(new Date());
System.out.println(time + " 当前线程:" + Thread.currentThread().getName() + "获得了写锁,count:" + count);
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 释放写锁
lock.writeLock().unlock();
}
}
}
public class MyThreadTest {
public static void main(String[] args) {
Counter counter = new Counter();
Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
counter.read();
}
});
Thread threadB = new Thread(new Runnable() {
@Override
public void run() {
counter.write();
}
});
threadA.start();
threadB.start();
}
}
看一下运行结果:
2023-10-23 16:36:08:786 当前线程:Thread-0获得了读锁,count:0
2023-10-23 16:36:13:791 当前线程:Thread-1获得了写锁,count:1
从日志时间上可以很清晰的看到,两个线程进行串行执行,证明了读和写之间是互斥的。
三、小结
总结下来,ReadWriteLock有以下特点:
- 允许多个线程在没有写入时同时读取,可以提高读取效率
- 当存在写入情况时,只允许一个线程写入,其他线程进入排队等待
- 适合读多写少的场景
对于同一个数据,有大量线程读取,但仅有少数线程修改,使用ReadWriteLock可以显著的提升程序并发执行效率。
例如,一个论坛的帖子,浏览可以看做读取操作,是非常频繁的,而回复可以看做写入操作,它是不频繁的,这种情况就可以使用ReadWriteLock来实现。
本文主要围绕ReadWriteLock的基本使用做了一次知识总结,如果有不正之处,请多多谅解,并欢迎批评指出。
四、参考
1、https://www.cnblogs.com/xrq730/p/4855631.html
2、https://www.liaoxuefeng.com/wiki/1252599548343744/1306581002092578
多线程系列(十) -ReadWriteLock用法详解的更多相关文章
- DAX/PowerBI系列 - 查询参数用法详解(Query Parameter)
PowerBI - 查询参数用法详解(Query Parameter) 很多人都不知道查询参数用来干啥,下面总结一下日常项目中常用的几个查询参数的地方.(本人不太欢hardcode的东西) 使用查询 ...
- 多线程java的concurrent用法详解(转载)
我们都知道,在JDK1.5之前,Java中要进行业务并发时,通常需要有程序员独立完成代码实现,当然也有一些开源的框架提供了这些功能,但是这些依然没有JDK自带的功能使用起来方便.而当针对高质量Java ...
- java多线程管理 concurrent包用法详解
我们都知道,在JDK1.5之前,Java中要进行业务并发时,通常需要有程序员独立完成代码实现,当然也有一些开源的框架提供了这些功能,但是这些依然没有JDK自带的功能使用起来方便.而当针对高质量 ...
- 精通awk系列(12):awk getline用法详解
回到: Linux系列文章 Shell系列文章 Awk系列文章 getline用法详解 除了可以从标准输入或非选项型参数所指定的文件中读取数据,还可以使用getline从其它各种渠道获取需要处理的数据 ...
- ASP.NET MVC深入浅出系列(持续更新) ORM系列之Entity FrameWork详解(持续更新) 第十六节:语法总结(3)(C#6.0和C#7.0新语法) 第三节:深度剖析各类数据结构(Array、List、Queue、Stack)及线程安全问题和yeild关键字 各种通讯连接方式 设计模式篇 第十二节: 总结Quartz.Net几种部署模式(IIS、Exe、服务部署【借
ASP.NET MVC深入浅出系列(持续更新) 一. ASP.NET体系 从事.Net开发以来,最先接触的Web开发框架是Asp.Net WebForm,该框架高度封装,为了隐藏Http的无状态模 ...
- jQuery 事件用法详解
jQuery 事件用法详解 目录 简介 实现原理 事件操作 绑定事件 解除事件 触发事件 事件委托 事件操作进阶 阻止默认事件 阻止事件传播 阻止事件向后执行 命名空间 自定义事件 事件队列 jque ...
- Ubuntu kill命令用法详解
转自:Ubuntu kill命令用法详解 1. kill 作用:根据进程号杀死进程 用法: kill [信号代码] 进程ID root@fcola:/# ps -ef | grep sen ...
- Android GLSurfaceView用法详解(二)
输入如何处理 若是开发一个交互型的应用(如游戏),通常需要子类化 GLSurfaceView,由此可以获取输入事件.下面有个例子: java代码: package eoe.ClearTes ...
- 教程-Delphi中Spcomm使用属性及用法详解
Delphi中Spcomm使用属性及用法详解 Delphi是一种具有 功能强大.简便易用和代码执行速度快等优点的可视化快速应用开发工具,它在构架企业信息系统方面发挥着越来越重要的作用,许多程序员愿意选 ...
- selenium用法详解
selenium用法详解 selenium主要是用来做自动化测试,支持多种浏览器,爬虫中主要用来解决JavaScript渲染问题. 模拟浏览器进行网页加载,当requests,urllib无法正常获取 ...
随机推荐
- vCenter 6.7 使用Grafana监控失败的处理
背景 国庆处理的vCenter监控. 老的vCenter6.0的平台很正常. 但是新的vCenter 6.7 就经常出现断连的情况. 花费了快一个多小时才搞定, 这里记录一下. 问题现象 vCente ...
- 每日一道Java面试题:方法重载与方法重写,这把指定让你明明白白!
写在开头 请聊一聊Java中方法的重写和重载? 这个问题应该是各大厂面试时问的最多的话题之一了,它们几乎贯穿了我们日常的开发工作,在过往的博客中我们多多少少都提到过重载与重写,而今天我们就一起来详细的 ...
- ChatGPT背后的AI背景、技术门道和商业应用(万字长文,建议收藏)
作者:京东科技 李俊兵 各位看官好,我是球神(江湖代号). 自去年11月30日ChatGPT问世以来,迅速爆火出圈. 起初我依然以为这是和当年Transformer, Bert一样的"热点& ...
- js判断一个时间是否在某一个时间段内
很多时候,我们需要对时间进行处理: 比如说:获取当前的时间 判断某一个时间是否在一段时间内:如果在显示出某一个按钮: 让用户可以操作:如果不在,按钮隐藏 这个时候,我们就需要对时间进行处理了 < ...
- 【JS 逆向百例】某音 X-Bogus 逆向分析,JSVMP 纯算法还原
声明 本文章中所有内容仅供学习交流使用,不用于其他任何目的,不提供完整代码,抓包内容.敏感网址.数据接口等均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关! 本文章未经许 ...
- 超级AI助手:全新提升!中文NLP训练框架,快速上手,海量训练数据
"超级AI助手:全新提升!中文NLP训练框架,快速上手,海量训练数据,ChatGLM-v2.中文Bloom.Dolly_v2_3b助您实现更智能的应用!" 1.简介 目标:基于py ...
- 2.3 Windows驱动开发:内核字符串转换方法
在内核编程中字符串有两种格式ANSI_STRING与UNICODE_STRING,这两种格式是微软推出的安全版本的字符串结构体,也是微软推荐使用的格式,通常情况下ANSI_STRING代表的类型是ch ...
- (转)时代的见证:集成更新的Windows 7旗舰版、专业版镜像
制作缘起:微软曾于2019年提供过两份内部集成更新的英文旗舰版.专业版镜像(参见:集成IE11+最新补丁:微软新版Windows 7镜像泄露),方便用户安装,缩短更新过程.经我们下载安装研究发现,这两 ...
- ElasticSearch7.3学习(十七)----搜索结果字段解析及time_out字段解析
1.搜索结果字段解析 首先插入一条测试数据 PUT /my_index/_doc/1 { "title": "2019-09-10" } 然后无条件搜索所有 G ...
- JS leetcode 移除元素 题解分析
壹 ❀ 引 又到了每日一道算法题的环节,今天做的题同样非常简单,题目来源leetcode27. 移除元素,题目描述如下: 给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 va ...