【漫画】互斥锁ReentrantLock不好用?试试读写锁ReadWriteLock

ReentrantLock完美实现了互斥,完美解决了并发问题。但是却意外发现它对于读多写少的场景效率实在不行。此时ReentrantReadWriteLock来救场了!一种适用于读多写少场景的锁,可以大幅度提升并发效率,你必须会哦!

序幕

_1

为何引入读写锁?

ReentrantReadWriteLock,顾名思义,是可重用的读写锁。

在读多写少的场合,读写锁对系统性能是很有好处的。因为如果系统在读写数据时均只使用独占锁,那么读操作和写操作间、读操作和读操作间、写操作和写操作间均不能做到真正的并发,并且需要相互等待。而读操作本身不会影响数据的完整性和一致性。

因此,理论上讲,在大部分情况下,应该可以允许多线程同时读,读写锁正是实现了这种功能。

划重点:读写锁适用于读多写少的情况。可以优化性能,提升易用性。

读写锁 ReadWriteLock

读写锁,并不是 Java 语言特有的,而是一个广为使用的通用技术,所有的读写锁都遵守以下三条基本原则:

允许多个线程同时读共享变量;

只允许一个线程写共享变量;

如果一个写线程正在执行写操作,此时禁止读线程读共享变量。

读写锁与互斥锁的一个重要区别就是读写锁允许多个线程同时读共享变量,而互斥锁是不允许的,这是读写锁在读多写少场景下性能优于互斥锁的关键。但读写锁的写操作是互斥的、独占的,当一个线程在写共享变量的时候,是不允许其他线程执行写操作和读操作。只要没有写操作,读取锁可以由多个读线程同时保持。读写锁访问约束如下表所示:

读写锁 读 写
  非阻塞   阻塞  
  阻塞   阻塞  

读写锁维护了一对相关的锁,一个用于只读操作,一个用于写入操作。

private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); //读锁 private final Lock r = rwl.readLock(); //写锁 private final Lock w = rwl.writeLock();

为了对比读写锁和独占锁的区别,我们可以写一个测试代码,分别传入ReentrantLock 和 ReadLock,对比一下总耗时。

private static final ReentrantLock lock = new ReentrantLock(); private static final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); private static final Lock r = rwl.readLock(); public static String read(Lock lock, String key) throws InterruptedException { r.lock(); try { // 模拟读耗时多的场景 更能看出区别 Thread.sleep(1000 * 10); return m.get(key); } finally { r.unlock(); } } 快速实现一个缓存

回想一下工作中经常用到的缓存,例如缓存元数据,不就是一种典型的读多写少应用场景吗?缓存之所以能提升性能,一个重要的条件就是缓存的数据一定是读多写少的,例如元数据和基础数据基本上不会发生变化(写少),但是使用它们的地方却很多(读多)。

我们是不是可以用ReentrantReadWriteLock来手写一个缓存呢?先画一张图模拟简单的缓存流程吧:

未命名文件.png


_2

_3

String get(String key) throws InterruptedException { String v = null; r.lock(); log.info("{}获取读锁 time={}",Thread.currentThread().getName(),System.currentTimeMillis()); try { v = m.get(key); } finally { r.unlock(); log.info("{}释放读锁 time={}",Thread.currentThread().getName(),System.currentTimeMillis()); } if (v != null) { log.info("{}缓存存在,返回结果 time={}",Thread.currentThread().getName(),System.currentTimeMillis()); return v; } w.lock(); log.info("{}缓存中不存在,查询数据库,获取写锁 time={}",Thread.currentThread().getName(),System.currentTimeMillis()); try { log.info("{}二次验证 time={}",Thread.currentThread().getName(),System.currentTimeMillis()); v = m.get(key); if (v == null) { log.info("{}查询数据库完成 time={} ",Thread.currentThread().getName(),System.currentTimeMillis()); v = "value"; log.info("-------------验证写锁占有的时候 其他线程无法执行写操作和读操作----------------"); Thread.sleep(1000*5); m.put(key, v); } } finally { log.info("{}写锁释放 time={}",Thread.currentThread().getName(),System.currentTimeMillis()); w.unlock(); } return v; }

_5

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zggyjx.html