查看原文
其他

【原创】你所不知道的读写锁

告白 java进阶架构师 2022-04-29

点击上方“java进阶架构师”,选择右上角“置顶公众号

20大进阶架构专题每日送达

大家好,我是告白,算起来自己真的是好久没有写作了,最近承蒙师长大大指点,决定还是记录一下自己的一些感想感悟。也决定好好打磨一下自己的写作水平,与大家共同进步!


今天呢,我们来讲讲并发编程的读写锁-ReadWriteLock

那么什么是读写锁

  1. 我们知道锁基本上是排他性的(同一时刻只允许一个线程进行访问),而读写锁在同一时刻可以允许多个线程访问,但是在写线程访问时,所有的读线程和其他写线程均被阻塞。读写锁维护了一对锁,一把写锁和一把读锁,通过

    分离

    读锁和写锁,使得并发性相比于一般的锁有了很大的提升。与互斥锁相比,读写锁是否能够提高性能取决于读写数据的频率、读取和写入操作的持续时间、以及读线程和写线程之间的竞争。

  1. 互斥原则

  • 读-读能共存

  • 读-写不能共存

  • 写-写不能共存

  1. ReadWriteLock 接口源码

public interafce ReadWriter{

Lock readLock();

Lock writeLock();
}
  1. 示例如下:

public class TestReadWriteLock {
public static void main(String[] args){
final ReadWriteLockDemo rwd = new ReadWriteLockDemo();
//启动100个读线程
for (int i = 0; i < 100; i++) {
new Thread(() -> rwd.get()).start();
}
//
写线程
new Thread(() -> rwd.set((int)(Math.random()*101)),"Write").start();
}
}

class ReadWriteLockDemo{
//模拟共享资源--Number
private int number = 0;
// 实际实现类--ReentrantReadWriteLock,默认非公平模式
private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

//读锁
public void get(){
//使用读锁
readWriteLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName()+" : "+number);
}finally {
readWriteLock.readLock().unlock();
}
}
//写锁
public void set(int number){
readWriteLock.writeLock().lock();
try {
this.number = number;
System.out.println(Thread.currentThread().getName()+" : "+number);
}finally {
readWriteLock.writeLock().unlock();
}
}
}

测试结果:

Thread-1 : 0 Thread-2 : 0 Thread-3 : 0 Thread-0 : 0 Thread-4 : 0 Thread-5 : 0 Thread-9 : 0 Thread-10 : 0 Thread-7 : 0 Thread-8 : 0 Thread-6 : 0 Thread-11 : 0 Thread-12 : 0 Thread-13 : 0 Thread-14 : 0 Thread-15 : 0 Thread-16 : 0 Thread-17 : 0 Thread-21 : 0 Thread-19 : 0 Thread-20 : 0 Thread-18 : 0 Thread-24 : 0 Thread-23 : 0 Thread-27 : 0 Thread-22 : 0 Thread-30 : 0 Thread-25 : 0 Thread-28 : 0 Thread-31 : 0 Thread-33 : 0 Thread-34 : 0 Thread-32 : 0 Thread-35 : 0 Thread-29 : 0 Thread-26 : 0 Thread-36 : 0 Thread-37 : 0 Thread-38 : 0 Thread-39 : 0 Thread-40 : 0 Thread-41 : 0 Thread-43 : 0 Thread-45 : 0 Thread-42 : 0 Thread-44 : 0 Thread-49 : 0 Thread-51 : 0 Thread-50 : 0 Thread-52 : 0 Thread-48 : 0 Thread-47 : 0 Thread-46 : 0 Thread-55 : 0 Thread-53 : 0 Thread-57 : 0 Thread-54 : 0 Thread-56 : 0 Thread-58 : 0 Thread-59 : 0 Thread-60 : 0 Thread-61 : 0 Thread-63 : 0 Thread-62 : 0 Thread-64 : 0 Thread-65 : 0 Thread-66 : 0 Thread-68 : 0 Thread-70 : 0 Thread-71 : 0 Thread-67 : 0 Thread-69 : 0 Thread-72 : 0 Thread-77 : 0 Thread-75 : 0 Thread-73 : 0 Thread-74 : 0 Thread-76 : 0 Thread-81 : 0 Thread-78 : 0 Thread-79 : 0 Thread-80 : 0 Thread-82 : 0 Thread-83 : 0 Thread-84 : 0 Thread-85 : 0 Thread-86 : 0 Thread-87 : 0 Thread-89 : 0 Thread-90 : 0 Thread-91 : 0 Thread-92 : 0 Thread-88 : 0 Thread-93 : 0 Thread-94 : 0 Thread-99 : 0 Thread-97 : 0 Thread-98 : 0 Thread-95 : 0 Thread-96 : 0 Write : 99

因为首先启动读线程时,此时number为0;然后某个时刻写线程修改了共享资源number数据,读线程再次读取最新值!

打铁趁热,接下来聊聊ReadWriteLock接口的经典的实现类-ReentrantReadWriteLock

ReentrantReadWriteLock 特性

  • 非公平模式(默认)

构造为非公平策略时,读写锁的入口顺序未指定,这取决于可重入性约束。持续竞争的非公平锁可以无限期地延迟一个或多个读写器线程,但通常具有比公平锁更高的吞吐量。

  • 公平模式

构造为公平策略时,线程使用近似的到达顺序策略(队列策略)争夺输入。当释放当前持有的锁时,要么最长等待的单个写入线程将被分配写锁,或者如果有一组读取线程等待的时间比所有等待的写入线程都长,那么该组读线程组将被分配读锁。

如果写入锁被占有,或者存在等待写入线程,则试图获取公平读取锁(非可重入)的线程将阻塞。直到当前等待写入线程中最老的线程获取并释放写入锁之后,该线程才会获取读取锁。当然,如果等待的写入线程放弃等待,剩下一个或多个读取器线程作为队列中最长的等待器而没有写锁,那么这些读取器将被分配读锁。

构造器的源码如下:

public ReentrantReadWriteLock() {
this(false);
}

//创建ReentrantReadWriteLock,true--公平 false-nonfair
public ReentrantReadWriteLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
readerLock = new ReadLock(this);
writerLock = new WriteLock(this);
}

可以看到,默认的构造方法使用的是非公平模式,创建的Sync是NonfairSync对象,然后初始化读锁和写锁。

  1. public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; }

  2. public ReentrantReadWriteLock.ReadLock readLock() { return readerLock; }

  • 可重入

顾名思义,就是支持重入的锁,它表示该线程能够支持一个线程对资源的重复加锁。

这个锁允许读线程和写线程以ReentrantLock的语法重新获取读写锁。在写入线程保持的所有写入锁被释放之前,不允许不可重入的读线程。

另外,写锁(写线程)可以获取读锁,但是不允许读锁(读线程)获取写锁。在其他应用程序中,当对在读锁下执行读取的方法或回调期间保持写锁时,可重入性可能非常有用。

  • 锁降级

锁降级指的是写锁降级为读锁。如果当前线程拥有写锁,然后将其释放,最后在获取读锁,这种分段完成的过程不能称之为锁的降级。锁降级是指当前拥有写锁,在获取到读锁,随后释放(先前拥有的锁)写锁的过程。但是,从读锁到写锁的升级是不可能的。

  • 支持中断

在读锁和写锁的获取过程中支持中断 。

  • 支持Condition

后续文章给出,敬请期待哦~

下面给出一个锁降级的例子和使用场景来结束今天的例子哈~~

锁降级:

class CachedData {
Object data;
//volatile修饰,保持内存可见性
volatile boolean cacheValid;
//可重入读写锁-读写锁的常用实现类
final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();

void processCachedData() {
//首先获取读锁
rwl.readLock().lock();
//没有缓存数据则放弃读锁,获取写锁
if (!cacheValid) {
// Must release read lock before acquiring write lock
rwl.readLock().unlock();
rwl.writeLock().lock();
try {
// Recheck state because another thread might have
// acquired write lock and changed state before we did.
if (!cacheValid) {
data = ...
cacheValid = true;
}
rwl.readLock().lock();
} finally {
//进行锁降级
rwl.writeLock().unlock();
// Unlock write, still hold read
}
}

try {
use(data);
} finally {
rwl.readLock().unlock();
}
}
}
}

使用场景:

class DictionaryDemo {
//集合对象
private final Map<String, Data> m = new TreeMap<String, Data>();
//读写锁
private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
//获取读锁
private final Lock r = rwl.readLock();
//获取写锁
private final Lock w = rwl.writeLock();

public Data get(String key) {
r.lock();
try { return m.get(key); }
finally { r.unlock(); }
}
public String[] allKeys() {
r.lock();
try { return m.keySet().toArray(); }
finally { r.unlock(); }
}
public Data put(String key, Data value) {
w.lock();
try { return m.put(key, value); }
finally { w.unlock(); }
}
public void clear() {
w.lock();
try { m.clear(); }
finally { w.unlock(); }
}
}
}

总之,通常可以在集合使用场景中看到ReentrantReadWriteLock的身影。不过只有在集合比较大,读操作比写操作多,操作开销大于同步开销的时候才是值得的。还是的看具体的使用场景来决定你用什么锁。大家如果有兴趣可以去阅读一下其源码实现。好了,今天分享就到此结束,谢谢大家观看!


————  e n d ————

快年底了,师长为大家准备了三份面试宝典:

  • 《java面试宝典5.0》

  • 《350道Java面试题:整理自100+公司》

  • 《资深java面试宝典-视频版》

分别适用于初中级,中高级,以及资深级工程师的面试复习。

内容包含java基础、javaweb、各个性能优化、JVM、锁、高并发、反射、Spring原理、微服务、Zookeeper、数据库、数据结构、限流熔断降级等等。

获取方式:点“在看”,V信关注师长的小号:编程最前线并回复 面试 领取,更多精彩陆续奉上。

一、初中级《java面试宝典5.0》,对标8-13K

二、中高级《350道Java面试题:整理自100+公司》,对标12-20K

三、资深《java面试突击-视频版》,对标20K+


在看好不好,喵~ 

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存