第二部分:并发工具类17->ReadWriteLock:如何快速实现一个完备的缓存
1.其他工具类
用途:分场景优化性能,提升易用性
2.并发场景,读多写少
使用缓存,缓存元数据,缓存基础数据
缓存的数据一定是读多写少
3.读写锁ReadWriteLock
非常容易使用,性能很好
1.允许多个线程同时读共享变量
2.只允许一个线程写共享变量
3.如果一个写线程正在执行写操作,此时禁止读线程读共享变量
读写锁与互斥锁ReentrantLock重要区别就是读写锁允许多个线程同时读共享变量,互斥锁不允许
读写锁在读多写少场景下性能优于互斥锁的关键
读写锁的写操作是互斥的,当一个线程在写共享变量时,不允许其他线程执行写操作和读操作
4.使用读写锁ReadWriteLock,用在缓存上
Cache<K,V> 类,参数K代表缓存里key的类型,V代表缓存value的类型,都是泛型
缓存数据保存在Cache类的内部hashmap中,hashmap不是线程安全,使用读写锁ReadWriteLock保证线程安全
ReadWriteLock是接口,实现类ReentrantReadWriteLock,
缓存类的方法get和pu都用到了读写锁
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(); }
}
}
5.缓存的扩展知识
1.解决缓存数据初始化问题,可以采用一次性加载,也可以使用按需加载
2.源头数据不大,可以一次性加载方式,这种方式最简单,应用启动时把源头数据查出来,依次调用put方法就可以
3.源头数据大,按需加载,也成为懒加载,当应用查询缓存,数据不在缓存里,才触发加载源头相关数据进缓存
6.缓存按需加载
数据源头是数据库,如果缓存中没有缓存目标对象,就需要从数据库中加载,然后写入缓存,写缓存需要用到写锁,w.lock()
在获取写锁后,没有直接查库,而是重新验证了一次缓存中是否存在,不存在才会去查库?
高并发下,多线程竞争写锁,缓存是空的,线程T1获取写锁后,直接查询并更新缓存,释放锁,线程T2会再次获取到锁,如果不验证,会再次查库
所以多加一部验证,能避免高并发场景下重复查询数据的问题
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;
}
}
7.锁的升级
先获取读锁,然后再获取写锁;这种是不允许的,ReadWriteLock
8.总结
读写锁类似ReentrantLock,也支持公平锁和非公平锁
只有写锁支持条件变量,读锁不支持条件变量
newCondition()
双写方案,写缓存+写数据库
超时机制,缓存不是长久有效,缓存的数据超过时效,缓存中就实效了。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 记一次.NET内存居高不下排查解决与启示