读写锁之ReadWriteLock

你可能有这样一个疑问,Java SDK 并发包里为什么还有很多其他的工具类呢?原因很简单:分场景优化性能,提升易用性

针对读多写少这种并发场景,Java SDK 并发包提供了读写锁——ReadWriteLock
读写锁,并不是 Java 语言特有的,而是一个广为使用的通用技术,所有的读写锁都遵守以下三条,尤其注意第三条,之后我们讲跟其他锁的对比会用到。
基本原则:
1. 允许多个线程同时读共享变量;
2.只允许一个线程写共享变量;
3. 如果一个写线程正在执行写操作,此时禁止读线程读共享变量(读锁 写锁是互斥的,不能同时存在)
 

互斥锁:

 
 

互斥锁

互斥锁

升降级

ReadWriteLock

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

写操作是互斥的,当一个

线程在写共享变量的时候,是不允许其他线程执行写操作和读操作。

不允许升级,允许降级。

读锁不支持条件变量newCondition()

 
 
 
一:用 ReadWriteLock 快速实现一个通用的缓存工具类
 1 class MyCache<K,V> {
 2     
 3     final Map<K, V> m = new HashMap<>();
 4     final ReadWriteLock rwl = new ReentrantReadWriteLock();
 5     // 读锁 
 6     final Lock readLock = rwl.readLock();
 7     
 8     // 写锁 
 9     final Lock writeLock = rwl.writeLock();
10     // 读缓存
11     V get(K key) {
12         readLock.lock();
13         try { return m.get(key); }
14         finally { 
15             readLock.unlock(); 
16         }
17     }
18     
19     // 写缓存
20     V put(String key, Data v) {
21         writeLock.lock();
22         try { return m.put(key, v); }
23         finally { 
24             writeLock.unlock();
25         }
26     }
27 }
View Code
面的这段代码实现了按需加载的功能,这里我们假设缓存的源头是数据库。先从缓存中获取数据,若缓存中没有,就从数据库中加载,然后写入缓存。
经验之谈:在获取写锁之后,并不直接查数据库,而是再次验证缓存是否存在,为什么呢?
 1 class MyCache<K,V> {
 2 final Map<K, V> m =  new HashMap<>();
 3 final ReadWriteLock rwl =  new ReentrantReadWriteLock();
 4 final Lock r = rwl.readLock();
 5 final Lock w = rwl.writeLock();
 6  V get(K key) {
 7         V v = null;
 8           // 读缓存
 9           r.lock(); ①
10           try {
11              v = m.get(key); ②
12              } finally{
13                  r.unlock(); ③
14              }
15           // 缓存中存在,返回
16           if(v != null) { ④
17               return v;
18               }
19           // 缓存中不存在,查询数据库
20           w.lock(); ⑤
21           try {
22               // 获取到写锁,再次验证 
23               v = m.get(key); ⑥
24               if(v == null){ ⑦
25                   //假设A1线程获取读锁,并更新了数据库,释放了读锁
26                   // 接下来A2线程得到读锁,获取读锁,并更新了数据库,释放了读锁
27                   // A3线程也是如此操作,
28                   // 获取到读锁再次验证是否存在,就能避免后续线程多次操作数据库
29                   // 查询数据库 代码省略
30                   //....................
31                   m.put(key, v);
32                   }
33               } finally{
34               w.unlock();
35               }
36           return v;
37           }
38  }
View Code
二:锁的升级,在获取读锁后,发现是空的,(没有释放读锁),继续获得写锁,执行后续操作。实际上这种操作是不允许的,因为读锁和写锁互斥
读锁还没有释放,此时获取写锁,会导致写锁永久等待,最终导致相关线程都被阻塞,永远也没有机会被唤醒。锁的升级是不允许的,这个你一定要注意。 
 
// 读缓存 2 
r.lock(); ①
try { v = m.get(key); ② if (v == null) { w.lock(); try { // 再次验证并更新缓存 9 // 省略详细代码 } finally{ w.unlock(); } } } finally{ r.unlock(); ③ }

三:锁的降级

不过,虽然锁的升级是不允许的,但是锁的降级却是允许的。以下代码来源自ReentrantReadWriteLock 的官方示例,伪代码如下
 1 class CachedData {
 2     Object data;
 3     volatile boolean cacheValid;
 4     final ReadWriteLock rwl = new ReentrantReadWriteLock();
 5     // 读锁
 6     final Lock r = rwl.readLock();
 7     // 写锁
 8     final Lock w = rwl.writeLock();
 9 
10     void processCachedData() {
11         // 获取读锁
12         r.lock();
13         if (!cacheValid) {
14             // 释放读锁,因为不允许读锁的升级
15             r.unlock();
16             // 获取写锁
17             w.lock();
18             try {
19                 // 再次检查状态
20                 if (!cacheValid) {
21                     data = ...
22                     cacheValid = true;
23                 }
24                 // 释放写锁前,降级为读锁
25                 // 降级是可以的
26                 r.lock(); ①
27             } finally {
28                 // 释放写锁
29                 w.unlock();
30             }
31         }
32         // 此处仍然持有读锁
33         try {use(data);}
34         finally {r.unlock();}
35     }
36 }
 
 
总结:
读写锁类似于 ReentrantLock,也支持公平模式和非公平模式。读锁和写锁都实现了java.util.concurrent.locks.Lock 接口,所以除了支持 lock() 方法外,tryLock()、lockInterruptibly() 等方法也都是支持的。但是有一点需要注意,那就是只有写锁支持条件变量,读锁是不支持条件变量的,读锁调用 newCondition() 会抛出 UnsupportedOperationException异常
 
文中部分代码引用:王宝令  ReadWriteLock:如何快速实现一个完备的缓存
 
 
 
==========================================================================           如果您觉得这篇文章对你有帮助,可以【关注我】或者【点赞】,希望我们一起在架构的路上,并肩齐行
==========================================================================
原文地址:https://www.cnblogs.com/amberJava/p/12355474.html