并发王者课 - 铂金 2:豁然开朗 -“晦涩难懂”的 ReadWriteLock 竟如此妙不可言
欢迎来到《并发王者课》,本文是该系列文章中的第 15 篇。
在上篇文章中,我们介绍了 Java 中锁的基础 Lock 接口。在本文中,我们将介绍 Java 中锁的另外一个重要的基本型接口,即 ReadWriteLock 接口。
在探索 Java 中的并发时,ReadWriteLock 无疑是重要的,然而理解它却并不容易。如果你此前曾经检索资料,应该会发现大部分的文章对它的描述都比较晦涩难懂,或连篇累牍的源码陈列,或隔靴搔痒的三言两语,既说不到重点,也说不清来龙去脉。
所以,在本文中我们会将介绍的重点放在对思路的理解上,而不是对源码的解读上。对于源码以及其背后的知识,我们将在后面的更高级的系列中进行讲解。
一、理解 ReadWriteLock 存在的价值
理解 ReadWriteLock,首页要理解它存在的意义是什么。换言之,它要解决什么问题。为此,我们不妨从下图着手一探究竟。
不知你看明白了没有,这幅图所表达的有三层含义:
大量线程在竞争同一份资源;
这些线程中有的是读请求,有的是写请求;
在多个线程的请求中,读请求明显高于写请求。
这样的场景是否似曾相识?没错,它就是典型的缓存应用场景。
众所周知,缓存的存在是为了提高应用的读写性能。一方面,我们需要通过缓存拦截大量的读数据的请求。另一方面,我们也需要不定期地更新缓存。但总体而言,更新缓存的次数远远小于读缓存的次数。
在这个过程中,关键问题在于,为了保持数据一致性,我们在读写缓存的时候,不能让读请求拿到脏数据,这就需要用到锁。然而,更关键的问题在于,虽然读写之间需要互斥,但读与读之间不可以互斥。
总结来说,这个问题主要有下面这几个要点:
数据允许多个线程同时读取,但只允许一个线程进行写入;
在读取数据的时候,不可以存在写操作或者写请求;
在写数据的时候,不可以存在读请求。
如果你对此仍然有些迷茫,那么下面这张图建议你收藏,这张图正是 ReadWriteLock 对问题的概述和它的解决方案,也是诠释 ReadWriteLock 最好的一幅图。
在你没有理解 ReadWriteLock 之前,你会觉得它十分晦涩且源码枯燥。然而,一旦你理解它要解决的问题,以及它所提供的方案后,你会发现它的设计竟然如此巧妙。它竟然设计了两种截然不同的锁,其中一把正如我们此前认知的那样是线程互斥的,而另一把锁竟然可以为多个线程所共享!两把锁的完美配合,解决了并发读写的场景问题。
在恍然大悟后,所谓源码不过是队列与共享,它们是 ReadWriteLock 的一种实现方式,而不是阻挡你理解的绊脚石。
二、自主实现 ReadWriteLock
在理解了 ReadWriteLock 背后的问题和它的解决思路之后,我们就可以完全抛开 JDK 中的源码自己实现一把读写锁。
在读锁lockRead()
中,是不允许有写请求或写操作的。如果有,那么读请求将进入等待。
而在lockWrite()
中,同时不允许读请求和其他写操作的存在,此时只允许有一个写请求。
以上就是读写锁简单的自主实现方式。当然,它是不完善的,只是基本的示例。它没有考虑到基本的线程重入问题,真实情况也比它复杂很多,但你理解它的意思就好。
三、Java 中的 ReadWriteLock 是如何实现的
最后,我们再来看 JDK 中的 ReadWriteLock 实现的一些基本思路。ReadWriteLock 和我们上篇所说的 Lock 接口以及其他类的基本关系如下图所示:
可以看到,JDK 中的读写锁的实现是在 ReentrantReadWriteLock 这个类中。ReentrantReadWriteLock 包含了两个内部类:ReadLock 和 WriteLock,而这两个类又实现了 Lock 接口。
读写锁的升级与降级
读写锁的升级与降级是 ReentrantReadWriteLock 中的一个重要知识点,也是高频的面试题。
从读锁到写锁,称之为锁的升级,反之为锁的降级。理解读写锁的升级和降级,最直观的方式是写代码验证。
代码片段 1,先获取读锁,再获取写锁。
输出结果如下:
代码片段 2,先获取写锁,再获取读锁:
输出结果如下:
这样一来,结果已经十分明了。ReentrantReadWriteLock 支持锁的降级,但不支持锁的升级。
读写锁中的公平性
在前面的文章中,我们讲过线程饥饿的由来和后果,所以良好的并发工具类在设计时都会考虑到公平性,ReentrantReadWriteLock 也是如此。
在 ReentrantReadWriteLock 中,同时提供了公平和非公平两种模式,且默认为非公平模式。从下面摘取的源码片段中,可以清晰地看到。
小结
以上就是关于读写锁的全部内容。在本文中,我们从缓存问题出发,接着从 ReadWriteLock 中寻找答案,以便能从更轻松的角度理解 ReadWriteLock 的来龙去脉。
理解 ReadWriteLock 的关键不在于对源码的剖析,而在于对其思路的理解。
另外,我们简单地介绍了 ReentrantReadWriteLock 中的一些关键知识点,但诸如其背后的 AQS 等并没有展开陈述。对此也不必着急,我们会在后面有详细的分析介绍。
正文到此结束,恭喜你又上了一颗星✨
夫子的试炼
尝试在示例代码中增加对读写线程的重入支持。
延伸阅读与参考资料
关于作者
关注公众号【庸人技术笑谈】,获取及时文章更新。记录平凡人的技术故事,分享有品质(尽量)的技术文章,偶尔也聊聊生活和理想。不贩卖焦虑,不做标题党。
如果本文对你有帮助,欢迎点赞、关注、监督,我们一起从青铜到王者。
版权声明: 本文为 InfoQ 作者【技术八点半】的原创文章。
原文链接:【http://xie.infoq.cn/article/e9146c2911d34b451a1f1bc40】。文章转载请联系作者。
评论