源码版本:OpenJDK 11
读写锁
之前分析过并发场景下高频使用的 Reentrantlock 源码解析 (juejin.cn),该类实现 Lock
接口,以达到独占获取的语义。
而这次分析ReentrantReadWriteLock
实现的是 ReadWriteLock
接口。
ReadWriteLock
我理解为是 Lock
在特定场景下的扩展,当然我们都知道这个场景就是读多写少。在读多写少的场景下,如果依旧是独占式获取资源,很显然会出现性能瓶颈。
ReadWriteLock
所表达的读写锁语义是在同一时刻允许被多个读访问,但是如果是写访问,则其他的写与读都会被阻塞。
至于为啥说
ReadWriteLock
是Lock
在读多写少场景下的扩展,因为接口唯二的两个方法的返回值都是Lock
啊,果然锁的大爹还得是Lock
。public interface ReadWriteLock { Lock readLock(); Lock writeLock(); }
读写锁的用处
比较典型读多写少场景想来应该是缓存了。项目里面使用缓存主要是为了提高访问某些资源的速度,常见的就是应用和数据库之间加一个缓存。
但是直接使用缓存会带来某些不一致的问题:
-
不同的缓存更新策略会带来不同的一致性问题
(这里列出了常见的缓存更新策略,有兴趣的可以自行查一下)
- Cache Aside Pattern(旁路缓存模式)
- Read/Write Through Pattern(读写穿透)
- Write Behind Pattern(异步缓存写入
-
缓存失效时,可能大量的读请求落到数据库击穿,造成缓存击穿。
如果是对一致性要求比较严格的情况,可以考虑使用读写锁。或避免缓存击穿,但是一般典型情况下可能只在设置缓存的节点直接使用独占锁。
写个假缓存的demo
public class ReadWriteLockDemo {
private Integer cache;
private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
public Integer getFromCache() {
lock.readLock().lock();
System.out.println(Thread.currentThread().getName() + ":开始读取缓存");
Integer res = cache;
System.out.println(Thread.currentThread().getName() + ":结束读取缓存");
lock.readLock().unlock();
if (cache == null) {
return loadCache();
}
return cache;
}
private Integer loadCache() {
lock.writeLock().lock();
Integer val;
if (cache == null) {
System.out.println(Thread.currentThread().getName() + ":开始加载数据库");
// get from database...
_load();
val = new Random(5).nextInt();
cache = val;
System.out.println(Thread.currentThread().getName() + ":结束加载数据库");
lock.writeLock().unlock();
} else {
lock.readLock().lock();
lock.writeLock().unlock();
System.out.println(Thread.currentThread().getName() + ":开始读取缓存");
val = cache;
System.out.println(Thread.currentThread().getName() + ":结束读取缓存");
lock.readLock().unlock();
}
return val;
}
private void _load() {
// 模拟读写数据库的耗时
try {
TimeUnit.SECONDS.sleep(1L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void setCache(Integer val) {
lock.writeLock().lock();
System.out.println(Thread.currentThread().getName() + ":开始写缓存"