欢送来到《并发王者课》,本文是该系列文章中的 第 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 推送行业深度好文。
如果本文对你有帮忙,欢送 点赞 、 关注 、 监督 ,咱们一起 从青铜到王者。