本文将介绍并发编程中的读写锁 ReadWriteLock。
一、适用场景
读多写少场景。
二、什么是读写锁?
读写锁提供了写锁和读锁,遵循以下三条基本原则:
- 同一时刻,允许多个线程读
- 同一时刻,只允许一个线程写
- 若有线程正在写,则禁止其它线程读
读写锁与互斥锁的一个重要区别是读写锁允许多个线程同时读,这是读写锁在读多写少场景下性能优于互斥锁的关键。
三、缓存的简单实现
- 声明一个
Cache<K, V>
类,其中 K 代表 key 的类型,V 代表 value 的类型
- 缓存数据存储于 HashMap 中,由于 HashMap 本身不是线程安全的,额外假如 ReadWriteLock 机制保证其安全
- ReadWriteLock 是一个接口,有实现类 ReentrantReadWriteLock
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| class Cache<K,V> { final Map<K, V> m = new HashMap<>(); final ReadWriteLock rwl = new ReentrantReadWriteLock();
final Lock r = rwl.readLock(); final Lock w = rwl.writeLock();
V get(K key) { r.lock(); try { return m.get(key); } finally { r.unlock(); } } V put(K key, V value) { w.lock(); try { return m.put(key, v); } finally { w.unlock(); } } }
|
四、按需加载缓存
1. 示例
在实际项目中,我们可能会使用这样一种按需加载缓存:向缓存中获取值时,缓存应该首先判断是否有此值,如果有直接返回,如果没有则加载后返回。
下面是利用 ReadWriteLock 实现的简单按需加载缓存:
- 首先获取读锁,读取缓存中对应的数据,释放读锁
- 当值存在时,直接返回
- 当值不存在时,获取写锁,再次验证值是否存在,加载值,放入缓存,释放写锁,返回
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| class Cache<K,V> { final Map<K, V> m = new HashMap<>(); final ReadWriteLock rwl = new ReentrantReadWriteLock();
final Lock r = rwl.readLock(); final Lock w = rwl.writeLock();
V get(K key) { V v = null; r.lock(); try { v = m.get(key); } finally { r.unlock(); } if (v != null) { return v; } w.lock(); try { v = m.get(key); if (v == null) { v = 查询数据库; m.put(key, v); } } finally { w.unlock(); } return v; } }
|
2. 为什么获取写锁后再次验证值是否存在?
可能存在这样一种情况,多个线程同时访问 get()
方法,均发现值不存在,其中一个线程率先获取读锁,填充值,后续线程再获取到读锁时值实质上已经存在,无需重复填充,
3. 为什么先获取写锁再加载数据?
通过这种方式,可以保证同一时刻只有一个线程在加载数据,避免 “被加载方” 的压力。
五、锁的升级与降级
1. 锁的升级
1 2 3 4 5 6 7 8 9 10 11 12
| r.lock(); try { w.lock(); try { } finally { w.unlock(); } } finally { r.unlock(); }
|
锁的升级:先获取读锁,然后在读锁未释放时获取写锁。
ReadWriteLock 并不支持锁的升级,对于 ReadWriteLock 来说,获取写锁的前提是所有读锁均已释放(即使读锁是线程自身持有)。
在上述示例中,读锁未释放时获取写锁,将导致写锁永久等待。
2. 锁的降级
1 2 3 4 5 6 7 8 9 10 11 12
| w.lock(); try { r.lock(); try { } finally { r.unlock(); } } finally { w.unlock(); }
|
ReadWriteLock 允许锁的降级,即先获取写锁,然后在写锁未释放时获取读锁。
参考