李林超博客
首页
归档
留言
友链
动态
关于
归档
留言
友链
动态
关于
首页
Java
正文
53.ReentrantReadWriteLock应用之缓存
Leefs
2022-11-27 PM
1011℃
0条
[TOC] ### 一、缓存更新策略 更新时,是先清缓存还是先更新数据库 **先清缓存,再更新数据库** ![53.ReentrantReadWriteLock应用之缓存01.png](https://lilinchao.com/usr/uploads/2022/11/1478598110.png) **结果**:造成查询的值和数据库中的值不一致 **先更新数据库,再清除缓存** ![53.ReentrantReadWriteLock应用之缓存02.png](https://lilinchao.com/usr/uploads/2022/11/438890966.png) **结果**:造成A线程首次查询和后续查询得到不一致的结果,首次查询得到 x=1,后续查询发现已经清空了缓存,需要去数据库中查得 x=2 补充一种情况,假设查询线程 A 查询数据时恰好缓存数据由于时间到期失效,或是第一次查询 ![53.ReentrantReadWriteLock应用之缓存03.png](https://lilinchao.com/usr/uploads/2022/11/3015933427.png) 这种情况的出现几率非常小,见 facebook 论文 #### 总结 - **先清缓存**:可能造成刚清理缓存还没有更新数据库,高并发下,其他线程直接查询了数据库过期数据到缓存中,这种情况非常严重,直接导致后续所有的请求缓存和数据库不一致。 - **先更新据库**:可能造成刚更新数据库,还没清空缓存就有线程从缓存拿到了旧数据,这种情况概率比较小,影响范围有限,只对这一次的查询结果有问题。 显而易见,通常情况下,先更新数据库,然后清空缓存。 ### 二、读写锁实现一致性缓存 使用读写锁实现一个简单的按需加载缓存 ```java class GenericCachedDao
{ // HashMap 作为缓存非线程安全, 需要保护 HashMap
map = new HashMap<>(); ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); GenericDao genericDao = new GenericDao(); public int update(String sql, Object... params) { SqlPair key = new SqlPair(sql, params); // 加写锁, 防止其它线程对缓存读取和更改 lock.writeLock().lock(); try { int rows = genericDao.update(sql, params); map.clear(); return rows; } finally { lock.writeLock().unlock(); } } public T queryOne(Class
beanClass, String sql, Object... params) { SqlPair key = new SqlPair(sql, params); // 加读锁, 防止其它线程对缓存更改 lock.readLock().lock(); try { T value = map.get(key); if (value != null) { return value; } } finally { lock.readLock().unlock(); } // 加写锁, 防止其它线程对缓存读取和更改 lock.writeLock().lock(); try { // get 方法上面部分是可能多个线程进来的, 可能已经向缓存填充了数据 // 为防止重复查询数据库, 再次验证 T value = map.get(key); if (value == null) { // 如果没有, 查询数据库 value = genericDao.queryOne(beanClass, sql, params); map.put(key, value); } return value; } finally { lock.writeLock().unlock(); } } // 作为 key 保证其是不可变的 class SqlPair { private String sql; private Object[] params; public SqlPair(String sql, Object[] params) { this.sql = sql; this.params = params; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } SqlPair sqlPair = (SqlPair) o; return sql.equals(sqlPair.sql) && Arrays.equals(params, sqlPair.params); } @Override public int hashCode() { int result = Objects.hash(sql); result = 31 * result + Arrays.hashCode(params); return result; } } } ``` **注意** + 以上实现体现的是读写锁的应用,保证缓存和数据库的一致性,但有下面的问题没有考虑 + 适合读多写少,如果写操作比较频繁,以上实现性能低 + 没有考虑缓存容量 + 没有考虑缓存过期 + 只适合单机 + 并发性还是低,目前只会用一把锁 + 更新方法太过简单粗暴,清空了所有 key(考虑按类型分区或重新设计 key) + 乐观锁实现:用 CAS 去更新
标签:
并发编程
非特殊说明,本博所有文章均为博主原创。
如若转载,请注明出处:
https://lilinchao.com/archives/2634.html
上一篇
52.ReentrantReadWriteLock介绍
下一篇
54.ReentrantReadWriteLock实现原理详解
评论已关闭
栏目分类
随笔
2
Java
326
大数据
229
工具
31
其它
25
GO
47
NLP
4
标签云
Zookeeper
Spark RDD
GET和POST
Scala
Netty
字符串
稀疏数组
哈希表
Jenkins
Eclipse
BurpSuite
递归
Golang
数据结构和算法
ClickHouse
FastDFS
VUE
CentOS
Tomcat
设计模式
Kibana
SpringCloudAlibaba
DataX
查找
Golang基础
Hive
HDFS
LeetCode刷题
Filter
Hbase
友情链接
申请
范明明
庄严博客
Mx
陶小桃Blog
虫洞
评论已关闭