重要声明:本人之前对java中的读写锁也不是非常了解,用的也不是很多,尤其在读写锁的策略原理一块没有深究过,本篇文章是在学习【玩转Java并发工具,精通JUC,成为并发多面手】课程后写的,故文章类型选择为"转载",因为本文的很多结论都是来自于那门课程,请知悉~。希望对各位同仁有帮助~


 读写锁的基本使用

在【ReentrantLock锁详解】一文中讲到了java中锁的划分,本篇主要讲述共享锁和排他锁:ReentrantReadWriteLock

在ReentrantReadWriteLock中包含读锁和写锁,其中读锁是可以多线程共享的,即共享锁,而写锁是排他锁,在更改时候不允许其他线程操作。读写锁其实是一把锁,所以会有同一时刻不允许读写锁共存的规定。之所以要细分读锁和写锁也是为了提高效率,将读和写分离,对比ReentrantLock就可以发现,无论并发读还是写,它总会先锁住全部再说。

接着先来个代码演示下,读锁是共享锁,写锁是排他锁:

/*** ReentrantReadWriteLock读写锁示例**/
public class ReentrantReadWriteLockTest {private static ReentrantReadWriteLock reentrantLock = new ReentrantReadWriteLock();private static ReentrantReadWriteLock.ReadLock readLock = reentrantLock.readLock();private static ReentrantReadWriteLock.WriteLock writeLock = reentrantLock.writeLock();public static void read() {readLock.lock();try {System.out.println(Thread.currentThread().getName() + "获取读锁,开始执行");Thread.sleep(1000);} catch (Exception e) {e.printStackTrace();} finally {readLock.unlock();System.out.println(Thread.currentThread().getName() + "释放读锁");}}public static void write() {writeLock.lock();try {System.out.println(Thread.currentThread().getName() + "获取写锁,开始执行");Thread.sleep(1000);} catch (Exception e) {e.printStackTrace();} finally {writeLock.unlock();System.out.println(Thread.currentThread().getName() + "释放写锁");}}public static void main(String[] args) {new Thread(() -> read(), "Thread1").start();new Thread(() -> read(), "Thread2").start();new Thread(() -> write(), "Thread3").start();new Thread(() -> write(), "Thread4").start();}
}

输出结果如下,线程1和线程2可以同时获取读锁,而线程3和线程4只能依次获取写锁,因为线程4必须等待线程3释放写锁后才能获取到锁:

Thread1获取读锁,开始执行
Thread2获取读锁,开始执行
Thread1释放读锁
Thread2释放读锁
Thread3获取写锁,开始执行
Thread3释放写锁
Thread4获取写锁,开始执行
Thread4释放写锁

 读锁的插队策略

设想如下场景:在非公平的ReentrantReadWriteLock锁中,线程2和线程4正在同时读取,线程3想要写入,拿不到锁(同一时刻是不允许读写锁共存的),于是进入等待队列,线程5不在队列里,现在过来想要读取,策略1是如果允许读插队,就是说线程5读先于线程3写操作执行,因为读锁是共享锁,不影响后面的线程3的写操作,这种策略可以提高一定的效率,却可能导致像线程3这样的线程一直在等待中,因为可能线程5读操作之后又来了n个线程也进行读操作,造成线程饥饿;策略2是不允许插队,即线程5的读操作必须排在线程3的写操作之后,放入队列中,排在线程3之后,这样能避免线程饥饿

事实上ReentrantReadWriteLock在非公平情况下,读锁采用的就是策略2:不允许读锁插队,避免线程饥饿。更加确切的说是:在非公平锁情况下,允许写锁插队,也允许读锁插队,但是读锁插队的前提是队列中的头节点不能是想获取写锁的线程。

以上还在非公平ReentrantReadWriteLock锁中,在公平锁中,读写锁都是是不允许插队的,严格按照线程请求获取锁顺序执行。

下面用代码演示一下结论:在非公平锁情况下,允许写锁插队,也允许读锁插队,但是读锁插队的前提是队列中的头节点不能是想获取写锁的线程

/*** ReentrantReadWriteLock读写锁插队策略测试**/
public class ReentrantReadWriteLockTest {private static ReentrantReadWriteLock reentrantLock = new ReentrantReadWriteLock();private static ReentrantReadWriteLock.ReadLock readLock = reentrantLock.readLock();private static ReentrantReadWriteLock.WriteLock writeLock = reentrantLock.writeLock();public static void read() {System.out.println(Thread.currentThread().getName() + "开始尝试获取读锁");readLock.lock();try {System.out.println(Thread.currentThread().getName() + "获取读锁,开始执行");Thread.sleep(20);} catch (Exception e) {e.printStackTrace();} finally {readLock.unlock();System.out.println(Thread.currentThread().getName() + "释放读锁");}}public static void write() {System.out.println(Thread.currentThread().getName() + "开始尝试获取写锁");writeLock.lock();try {System.out.println(Thread.currentThread().getName() + "获取写锁,开始执行");Thread.sleep(40);} catch (Exception e) {e.printStackTrace();} finally {System.out.println(Thread.currentThread().getName() + "释放写锁");writeLock.unlock();}}public static void main(String[] args) {new Thread(() -> write(), "Thread1").start();new Thread(() -> read(), "Thread2").start();new Thread(() -> read(), "Thread3").start();new Thread(() -> write(), "Thread4").start();new Thread(() -> read(), "Thread5").start();new Thread(() -> {Thread[] threads = new Thread[1000];for (int i = 0; i < 1000; i++) {threads[i] = new Thread(() -> read(), "子线程创建的Thread" + i);}for (int i = 0; i < 1000; i++) {threads[i].start();}}).start();}
}

以上测试代码就演示了,在非公平锁时,其一:同一时刻读写锁不能同时存在, 其二,读锁非常容易插队,但前提是队列中的头结点不能是想获取写锁的线程。

 锁的升降级

升降级是指读锁升级为写锁,写锁降级为度锁。在ReentrantReadWriteLock读写锁中,只支持写锁降级为读锁,而不支持读锁升级为写锁,如下代码测试所示:

/*** ReentrantReadWriteLock锁升降级测试**/
public class ReentrantReadWriteLockTest {private static ReentrantReadWriteLock reentrantLock = new ReentrantReadWriteLock();private static ReentrantReadWriteLock.ReadLock readLock = reentrantLock.readLock();private static ReentrantReadWriteLock.WriteLock writeLock = reentrantLock.writeLock();public static void read() {System.out.println(Thread.currentThread().getName() + "开始尝试获取读锁");readLock.lock();try {System.out.println(Thread.currentThread().getName() + "获取读锁,开始执行");Thread.sleep(20);System.out.println(Thread.currentThread().getName()+ "尝试升级读锁为写锁");//读锁升级为写锁(失败)writeLock.lock();System.out.println(Thread.currentThread().getName() +"读锁升级为写锁成功");} catch (Exception e) {e.printStackTrace();} finally {readLock.unlock();System.out.println(Thread.currentThread().getName() + "释放读锁");}}public static void write() {System.out.println(Thread.currentThread().getName() + "开始尝试获取写锁");writeLock.lock();try {System.out.println(Thread.currentThread().getName() + "获取写锁,开始执行");Thread.sleep(40);System.out.println(Thread.currentThread().getName() +"尝试降级写锁为读锁");//写锁降级为读锁(成功)readLock.lock();System.out.println(Thread.currentThread().getName()+ "写锁降级为读锁成功");System.out.println();} catch (Exception e) {e.printStackTrace();} finally {System.out.println(Thread.currentThread().getName() + "释放写锁");writeLock.unlock();readLock.unlock();}}public static void main(String[] args) {new Thread(() -> write(), "Thread1").start();new Thread(() -> read(), "Thread2").start();}
}

运行控制台输出:

Thread1开始尝试获取写锁
Thread1获取写锁,开始执行
Thread1尝试降级写锁为读锁
Thread1写锁降级为读锁成功
Thread1释放写锁Thread2开始尝试获取读锁
Thread2获取读锁,开始执行
Thread2尝试升级读锁为写锁

之所以ReentrantReadWriteLock不支持锁的升级(其它锁可以支持),主要是避免死锁,例如两个线程A和B都在读, A升级要求B释放读锁,B升级要求A释放读锁,互相等待形成死循环。如果能严格保证每次都只有一个线程升级那也是可以的。

 总结

  1. 读写锁特点特点:读锁是共享锁,写锁是排他锁,读锁和写锁不能同时存在
  2. 插队策略:为了防止线程饥饿,读锁不能插队
  3. 升级策略:只能降级,不能升级
  4. ReentrantReadWriteLock适合于读多写少的场合,可以提高并发效率,而ReentrantLock适合普通场合

 引申阅读:

Java中的共享锁和排他锁(以读写锁ReentrantReadWriteLock为例)相关推荐

  1. MySQL中的共享锁与排他锁

    2019独角兽企业重金招聘Python工程师标准>>> 在MySQL中的行级锁,表级锁,页级锁中介绍过,行级锁是Mysql中锁定粒度最细的一种锁,行级锁能大大减少数据库操作的冲突.行 ...

  2. mysql排他锁和共享锁视频_分享MySQL 中的共享锁和排他锁的用法

    在 MySQL 中的行级锁.表级锁和页级锁中,咱们介绍过,行级锁是 MySQL 中锁定粒度最细的一种锁,行级锁能大大减少数据库操作的冲突.行级锁分为共享锁和排他锁两种,本文将详细介绍共享锁和排他锁的概 ...

  3. 一文搞懂 mysql 中的共享锁、排他锁、悲观锁、乐观锁及使用场景

    目录 一.常见锁类型 二.Mysql引擎介绍 三.常用引擎间的区别 四.共享锁与排他锁 五.排他锁的实际应用 六.共享锁的实际应用 七.死锁的发生 八.另一种发生死锁的情景 九.死锁的解决方式 十.意 ...

  4. mysql 的独占锁和排它锁_数据库中的共享锁与排他锁

    摘要: 能修改数据.为什么要加锁很多人都知道,锁是用来解决并发问题的,那么什么是并发问题呢?并发情况下,不加锁会有什么问题呢?拿生活中的洗手间举例子,每个洗手间都会有一个门,并且是可以上锁的,当我们进 ...

  5. mysql排他锁怎么用_MySQL 中的共享锁和排他锁的用法

    在 MySQL 中的行级锁.表级锁和页级锁中,咱们介绍过,行级锁是 MySQL 中锁定粒度最细的一种锁,行级锁能大大减少数据库操作的冲突.行级锁分为共享锁和排他锁两种,本文将详细介绍共享锁和排他锁的概 ...

  6. mysql 事务排他锁_[数据库事务与锁]详解六: MySQL中的共享锁与排他锁

    注明: 本文转载自http://www.hollischuang.com/archives/923 在MySQL中的行级锁,表级锁,页级锁中介绍过,行级锁是Mysql中锁定粒度最细的一种锁,行级锁能大 ...

  7. Mysql中的共享锁和排他锁

    一.前言   刚开始学习MySQL中锁的时候,网上一查出来一堆,什么表锁.行锁.读锁.写锁.悲观锁.乐观锁等等等,直接整个人就懵了.好多文章都尽量把很多锁给列举一遍,生怕写少了内容不够丰富,有的连死锁 ...

  8. PHP程序中的文件锁、互斥锁、读写锁使用技巧解析

    文件锁全名叫 advisory file lock, 书中有提及. 这类锁比较常见,例如 mysql, php-fpm 启动之后都会有一个pid文件记录了进程id,这个文件就是文件锁. 这个锁可以防止 ...

  9. 【线程、锁】什么是AQS(锁分类:自旋锁、共享锁、独占锁、读写锁)

    文章目录 1. 什么是AQS 1.1 锁分类 1.2 具体实现 2. AQS底层使用了模板方法模式 3. AQS的简单应用 参考 1. 什么是AQS AQS:全称为AbstractQuenedSync ...

最新文章

  1. ipad html 自定义裁剪图片大小,移动端图片裁剪上传插件 Mavatar.js(原创)
  2. 与时代共振,AI助力工业缺陷检测
  3. 2019年六大新兴信息安全方向
  4. python去除rpm仓库中同名低版本的包
  5. linux 服务配置
  6. java bom json,JSON字符串带BOM头ufeff
  7. c语言中判断输入是否为数字_C语言编程判断回文数
  8. 《自己动手写操作系统》实践(一)
  9. 编写MQTT客户端程序——python
  10. aria2 txt导入_共一章 · mac下使用Aria2教程-迅雷和百度盘终极解决方案 · 看云
  11. 日语动词活用和变化规则及用法
  12. centos 7 拉黑IP
  13. 学习理论-PAC理论
  14. Python 第二十六章 面向对象 元类+反射+双下方法
  15. 击穿线程池面试题:3大方法,7大参数,4种拒绝策略
  16. 学生动物网页设计模板下载 大学生宠物HTML网页制作作品 简单宠物狗网页设计成品 dreamweaver学生网站模板
  17. mysql中函数row_number()如何使用它为结果集中的每一行生成序列号
  18. 2021校招滴滴笔试sql
  19. VB6.0 遇到“不能加载 MSCOMCTL.ocx“ 问题处理
  20. java可变参数 map_Java第6期Collection、Map、迭代器、泛型、可变参数、集合工具类、集合结构、Debug...

热门文章

  1. CST仿真学习 2021-10-12
  2. python设置中文字体的三种方法
  3. ux431黑苹果_UX设计中苹果领导地位的下降
  4. 爬虫----mumu模拟器如何开启root权限
  5. 苹果6怎样分屏_苹果iOS14测试版正式发布
  6. python爬虫遇到403错误
  7. 超宽屏幕比例_显示器长宽比是越来越大了吗
  8. 【每月更新】GitHub优秀项目推荐:2021年8月
  9. Overleaf登录报错: Please check that Google reCAPTCHA is not being blocked by an ad blocker or firewall.
  10. 考研英语——刷题看课流程