欢迎来到《并发王者课》,本文是该系列文章中的第15篇

在上篇文章中,我们介绍了Java中锁的基础Lock接口。在本文中,我们将介绍Java中锁的另外一个重要的基本型接口,即ReadWriteLock接口。

在探索Java中的并发时,ReadWriteLock无疑是重要的,然而理解它却并不容易。如果你此前曾经检索资料,应该会发现大部分的文章对它的描述都比较晦涩难懂,或连篇累牍的源码陈列,或隔靴搔痒的三言两语,既说不到重点,也说不清来龙去脉。

所以,在本文中我们会将介绍的重点放在对思路的理解上,而不是对源码的解读上。对于源码以及其背后的知识,我们将在后面的更高级的系列中进行讲解。

一、理解ReadWriteLock存在的价值

理解ReadWriteLock,首页要理解它存在的意义是什么。换言之,它要解决什么问题。为此,我们不妨从下图着手一探究竟。

不知你看明白了没有,这幅图所表达的有三层含义:

  • 大量线程在竞争同一份资源;
  • 这些线程中有的是读请求,有的是写请求
  • 在多个线程的请求中,读请求明显高于写请求

这样的场景是否似曾相识?没错,它就是典型的缓存应用场景

众所周知,缓存的存在是为了提高应用的读写性能。一方面,我们需要通过缓存拦截大量的读数据的请求。另一方面,我们也需要不定期地更新缓存。但总体而言,更新缓存的次数远远小于读缓存的次数

在这个过程中,关键问题在于,为了保持数据一致性,我们在读写缓存的时候,不能让读请求拿到脏数据,这就需要用到锁。然而,更关键的问题在于,虽然读写之间需要互斥,但读与读之间不可以互斥

总结来说,这个问题主要有下面这几个要点:

  • 数据允许多个线程同时读取,但只允许一个线程进行写入
  • 在读取数据的时候,不可以存在写操作或者写请求
  • 在写数据的时候,不可以存在读请求

如果你对此仍然有些迷茫,那么下面这张图建议你收藏,这张图正是ReadWriteLock对问题的概述和它的解决方案,也是诠释ReadWriteLock最好的一幅图。

在你没有理解ReadWriteLock之前,你会觉得它十分晦涩且源码枯燥。然而,一旦你理解它要解决的问题,以及它所提供的方案后,你会发现它的设计竟然如此巧妙。它竟然设计了两种截然不同的锁,其中一把正如我们此前认知的那样是线程互斥的,而另一把锁竟然可以为多个线程所共享!两把锁的完美配合,解决了并发读写的场景问题。

在恍然大悟后,所谓源码不过是队列与共享,它们是ReadWriteLock的一种实现方式,而不是阻挡你理解的绊脚石。

二、自主实现ReadWriteLock

在理解了ReadWriteLock背后的问题和它的解决思路之后,我们就可以完全抛开JDK中的源码自己实现一把读写锁。

public class ReadWriteLock{private int readers       = 0;private int writers       = 0;private int writeRequests = 0;public synchronized void lockRead() throws InterruptedException{while(writers > 0 || writeRequests > 0){wait();}readers++;}public synchronized void unlockRead(){readers--;notifyAll();}public synchronized void lockWrite() throws InterruptedException{writeRequests++;while(readers > 0 || writers > 0){wait();}writeRequests--;writers++;}public synchronized void unlockWrite() throws InterruptedException{writers--;notifyAll();}
}

在读锁lockRead()中,是不允许有写请求写操作的。如果有,那么读请求将进入等待。

而在lockWrite()中,同时不允许读请求和其他写操作的存在,此时只允许有一个写请求

以上就是读写锁简单的自主实现方式。当然,它是不完善的,只是基本的示例。它没有考虑到基本的线程重入问题,真实情况也比它复杂很多,但你理解它的意思就好。

三、Java中的ReadWriteLock是如何实现的

最后,我们再来看JDK中的ReadWriteLock实现的一些基本思路。ReadWriteLock和我们上篇所说的Lock接口以及其他类的基本关系如下图所示:

可以看到,JDK中的读写锁的实现是在ReentrantReadWriteLock这个类中。ReentrantReadWriteLock包含了两个内部类:ReadLock和WriteLock,而这两个类又实现了Lock接口。

读写锁的升级与降级

读写锁的升级与降级是ReentrantReadWriteLock中的一个重要知识点,也是高频的面试题。

从读锁到写锁,称之为锁的升级,反之为锁的降级。理解读写锁的升级和降级,最直观的方式是写代码验证。

代码片段1,先获取读锁,再获取写锁。

public class ReadWriteLockDemo {public static void main(String[] args) {ReadWriteLock readWriteLock = new ReentrantReadWriteLock();readWriteLock.readLock().lock();System.out.println("已经获取读锁...");readWriteLock.writeLock().lock();System.out.println("已经获取写锁...");}
}

输出结果如下:

已经获取读锁...

代码片段2,先获取写锁,再获取读锁:

public class ReadWriteLockDemo {public static void main(String[] args) {ReadWriteLock readWriteLock = new ReentrantReadWriteLock();readWriteLock.writeLock().lock();System.out.println("已经获取写锁...");readWriteLock.readLock().lock();System.out.println("已经获取读锁...");}
}

输出结果如下:

已经获取写锁...
已经获取读锁...Process finished with exit code 0

这样一来,结果已经十分明了。ReentrantReadWriteLock支持锁的降级,但不支持锁的升级

读写锁中的公平性

在前面的文章中,我们讲过线程饥饿的由来和后果,所以良好的并发工具类在设计时都会考虑到公平性,ReentrantReadWriteLock也是如此。

在ReentrantReadWriteLock中,同时提供了公平和非公平两种模式,且默认为非公平模式。从下面摘取的源码片段中,可以清晰地看到。

 public ReentrantReadWriteLock() {this(false);}/**/*** Creates a new {@code ReentrantReadWriteLock} with* default (nonfair) ordering properties.*/
public ReentrantReadWriteLock() {this(false);
}/*** Creates a new {@code ReentrantReadWriteLock} with* the given fairness policy.** @param fair {@code true} if this lock should use a fair ordering policy*/
public ReentrantReadWriteLock(boolean fair) {sync = fair ? new FairSync() : new NonfairSync();readerLock = new ReadLock(this);writerLock = new WriteLock(this);
}

小结

以上就是关于读写锁的全部内容。在本文中,我们从缓存问题出发,接着从ReadWriteLock中寻找答案,以便能从更轻松的角度理解ReadWriteLock的来龙去脉。

理解ReadWriteLock的关键不在于对源码的剖析,而在于对其思路的理解。

另外,我们简单地介绍了ReentrantReadWriteLock中的一些关键知识点,但诸如其背后的AQS等并没有展开陈述。对此也不必着急,我们会在后面有详细的分析介绍。

正文到此结束,恭喜你又上了一颗星✨

夫子的试炼

  • 尝试在示例代码中增加对读写线程的重入支持。

延伸阅读与参考资料

  • 示例代码参考
  • 《并发王者课》大纲与更新进度总览

关于作者

关注公众号【技术八点半】,及时获取文章更新。传递有品质的技术文章,记录平凡人的成长故事,偶尔也聊聊生活和理想。早晨8:30推送作者品质原创,晚上20:30推送行业深度好文。

如果本文对你有帮助,欢迎点赞关注监督,我们一起从青铜到王者

铂金02:豁然开朗-“晦涩难懂”的ReadWriteLock竟如此妙不可言相关推荐

  1. 并发王者课-铂金2:豁然开朗-“晦涩难懂”的ReadWriteLock竟如此妙不可言

    欢迎来到<并发王者课>,本文是该系列文章中的第15篇. 在上篇文章中,我们介绍了Java中锁的基础Lock接口.在本文中,我们将介绍Java中锁的另外一个重要的基本型接口,即ReadWri ...

  2. 曾经觉得学习晦涩难懂的我是如何爱上linux的

    2019独角兽企业重金招聘Python工程师标准>>> 2016年冬天,我已经是一名学习计算机科学与技术专业的大三的"老腊肉"了,但是当时的水平依旧平平.就在20 ...

  3. 「建议观看」史上超长,前端css晦涩难懂的点都在这啦

    前言 CSS大家肯定都是会的但是每个人所撑握的情况都不一样,特别是已经工作几年的前辈很多CSS玩法都不知道,可能他们已经习惯了用组件, 但是面试的时候又不可避免问,所以我整理了下CSS比较晦涩难懂的点 ...

  4. Transformer 中 比较晦涩难懂的东西

    文章目录 Transformer 中 比较晦涩难懂的东西 keras 实现mask encoder部分[对输入进行操作] decoder部分[对注意力矩阵操作] Output的输入 ==来自我亲爱的师 ...

  5. 曾经觉得学习晦涩难懂的我是如何爱上linux

    2016年冬天,我已经是一名学习计算机科学与技术专业的大三的"老腊肉"了,但是当时的水平依旧平平.就在2016年快要结束的时候,我周围的同学们被一股考研和工作的压力炸开了锅,我也在 ...

  6. 能把晦涩难懂的研究工作讲清楚,Distill就奖你10000美刀

    Root 编译整理 量子位 出品 | 公众号 QbitAI 在机器学习研究圈里,大家可能都有这样的赶脚: 大部分从事机器学习研究的人,不太擅长写作,无法清晰地呈现自己的研究工作. 为了奖励那些对于论文 ...

  7. 改善C++ 程序的150个建议学习之建议8:拒绝晦涩难懂的函数指针

    建议8:拒绝晦涩难懂的函数指针 在C/C++程序中,数据指针是最直接也是最常用的,理解起来也相对简单容易,但是函数指针理解起来却并不轻松.函数指针在运行时的动态调用中应用广泛,是一种常见而有效的手段. ...

  8. 晦涩难懂的设计软件T恤大集合,看看你能明白几个

    晦涩难懂的设计软件T恤大集合,看看你能明白几个 关键词:衣服 T恤 ps 软件 ┊ 图画类 ┊ 推荐: ┊ 来源:有意思吧 ┊ 收藏 Photoshop 透明T恤 ,为什么叫透明T恤呢,因为它有个橡皮 ...

  9. 【建议收藏】css晦涩难懂的点都在这啦

    首发原文链接: https://juejin.im/post/6888102016007176200 前言 CSS大家肯定都是会的但是每个人所撑握的情况都不一样,特别是已经工作几年的前辈(这里指的是我 ...

最新文章

  1. 在wamp环境下面安装Zend Optimizer的方法
  2. 区分什么是Apache、Tomcat,之间有什么关系?
  3. 现在可以插入MSN表情了
  4. React.js 小书 Lesson14 - 实战分析:评论功能(一)
  5. 用go语言制作读取excel模板批量生成word工具
  6. TCP协议的三次握手、四次挥手
  7. 做好项目,从正确定义问题开始!
  8. This will have no impact if delete.topic.enable is not set to true以及删除kafka中的topic
  9. MySQL数据库面试题
  10. 计算机二级MS Office中最难的是Excel?那是因为你没有掌握这些【重难点】!!!
  11. OpenAI 将 k8s 扩展至 7500 个节点以支持机器学习
  12. java中的 element_Java中队列的element()方法的用法
  13. android 定时唤醒蓝牙,Android保活——蓝牙唤醒(主动kill掉也可唤醒)
  14. Excel查询两列值的差异 -- VLOOPUP
  15. hysys动态模拟教程_(转载)HYSYS-过程模拟软件-稳态模拟-第一部分(一)
  16. java三国鼎立,网页游戏三国鼎立(武三国)一键服务端带教程及修改说明
  17. hexo部署时出现excepted token解决方法
  18. ab并发测试-Linux
  19. oracle中重做日志损坏,重做日志文件损坏的恢复笔记
  20. 地理信息系统矢量数据的组织形式 第一章:二维空间对象数据模型

热门文章

  1. 阿尔茨海默病临床试验中静息脑电节律的测量
  2. 【yum参数】【yum指令】【更换阿里源163源】【更换本地yum源】
  3. SVN客户端无法连接SVN服务器,主机积极拒绝
  4. 江南重工中船集团资产注入迷雾
  5. 支付宝集五福活动玩的套路你肯定不知道!
  6. java cms视频_【视频+源码】JAVA CMS系统项目实战
  7. 文件列表中查找同一批次号中批次号最小的文件名称
  8. java网络编程入门到精通
  9. java当前时间的时间戳_java获取当前时间(时间戳)的方法
  10. Xray捡洞中的高频漏洞