李林超博客
首页
归档
留言
友链
动态
关于
归档
留言
友链
动态
关于
首页
Java
正文
53.ReentrantReadWriteLock应用之缓存
Leefs
2022-11-27 PM
529℃
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
43
标签云
JavaWEB项目搭建
nginx
持有对象
LeetCode刷题
国产数据库改造
Filter
Tomcat
哈希表
Java
Eclipse
SpringBoot
锁
序列化和反序列化
Zookeeper
Spark SQL
Hbase
Jenkins
BurpSuite
高并发
Shiro
MyBatisX
VUE
RSA加解密
Elastisearch
Typora
线程池
Spark
稀疏数组
Python
Spark Streaming
友情链接
申请
范明明
庄严博客
Mx
陶小桃Blog
虫洞