java实现数据缓存
摘抄自java并发实战
有时候需要对数据缓存。用Map缓存数据比较合适。但是由于对吞吐量,一致性,计算性能的要求,对数据进行缓存的设计还是需要慎重考虑的。
一、利用HashMap加同步
(1)说明
把HashMap当作缓存容器。每缓存一个key的时候,都进行同步。
(2)代码
package memory; import java.util.HashMap;
import java.util.Map; /**
* Created by adrian.wu on 2018/12/12.
*/
public class MemoryFirst<K, V> implements Computable<K, V> {
private final Map<K, V> cache = new HashMap<>();
private final Computable<K, V> c; public MemoryFirst(Computable<K, V> c) {
this.c = c;
} @Override
public synchronized V compute(K arg) throws InterruptedException {
V result = cache.get(arg); if (result == null) {
result = c.compute(arg);
cache.put(arg, result);
} return result;
}
}
(3)缺点
由于HashMap并非线程安全,因此每一次计算都使用同步机制确保线程安全。很明显,这种方式伸缩性比较差。因为一个线程正在计算结果,其它所有线程都在等待,即使对应的arg是不同的。
二、用ConcurrentHashMap代替HashMap
(1)说明
ConcurrentHashMap是线程安全的,并且同步并非对整个Map进行同步而是对每一个分段进行同步,所以并发性也可以大大提升。
(2)代码
package memory; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; /**
* Created by adrian.wu on 2018/12/12.
*/
public class MemorySecond<K, V> implements Computable<K, V> {
private final Map<K, V> cache = new ConcurrentHashMap<>();
private final Computable<K, V> c; public MemorySecond(Computable<K, V> c) {
this.c = c;
} @Override
public V compute(K arg) throws InterruptedException {
V result = cache.get(arg); if (result == null) {
result = c.compute(arg);
cache.put(arg, result);
} return result;
}
}
(3)缺点
相比第一个设计方案。这种方案已经有很大的提升了。但是如果一个compute的计算开销很大,恰巧有另一个同一个arg的线程同时请求compute,则会造成重复计算,重复put的情况。所以我们希望如果有一个线程正在计算的时候另一个线程正在等待而不是重复计算。
三、利用FutureTask解决第二个设计的问题
(1)说明
利用FutrueTask, 如果get到结果则返回,如果正在计算则利用FutureTask的特性阻塞。否则计算。
(2)代码
package memory; import org.slf4j.Logger; import java.util.Map;
import java.util.concurrent.*; import static memory.ErrorHandler.launderThrowable; /**
* Created by adrian.wu on 2018/12/12.
*/
public class MemoryThird<K, V> implements Computable<K, V> {
private final Map<K, Future<V>> cache = new ConcurrentHashMap<>();
private final Computable<K, V> c; public MemoryThird(Computable<K, V> c) {
this.c = c;
} @Override
public V compute(final K arg) throws InterruptedException {
Future<V> f = cache.get(arg);
if (f == null) {
Callable<V> eval = new Callable<V>() {
@Override
public V call() throws Exception {
return c.compute(arg);
}
}; FutureTask<V> ft = new FutureTask<>(eval);
f = ft;
cache.put(arg, ft);
ft.run(); // start compute
}
try {
return f.get();
} catch (ExecutionException e) {
throw launderThrowable(e.getCause());
}
}
}
(3)缺点
只有一个缺陷,仍然存在两个线程计算出相同值的漏洞。就是由于compute方法中的if代码块仍然是非原子的“先检查,再执行”,因此仍然有可能两个线程在同一时间计算一个不存在的arg。原因是第23行的get方法和34行的put方法是对底层的Map操作,所以无法保证原子性。由于cache里面的是future而不是真正的值,所以将有可能导致缓存污染(cache pollution)问题,即如果某个计算过程被取消或者失败,那么缓存存入的Future是有缺陷的。
四、最终设计方案
(1)说明
使用putIfAbsent代替put,以保证原子性。如果发现Future计算被取消或失败则删除,从而缓存不会消耗过多内存。
(2)代码
package memory; import java.util.Map;
import java.util.concurrent.*; import static memory.ErrorHandler.launderThrowable; /**
* Created by adrian.wu on 2018/12/12.
*/
public class Memory<K, V> implements Computable<K, V> {
private Map<K, Future<V>> cache = new ConcurrentHashMap<>();
private Computable<K, V> c; public Memory(Computable<K, V> c) {
this.c = c;
} @Override
public V compute(K arg) throws InterruptedException {
while (true) {
Future<V> f = cache.get(arg); if (f == null) {
Callable<V> eval = new Callable<V>() {
@Override
public V call() throws Exception {
return c.compute(arg);
}
}; FutureTask<V> ft = new FutureTask<>(eval); f = cache.putIfAbsent(arg, ft); //double check
if (f == null) {
f = ft;
ft.run(); //start compute
}
}
try {
return f.get();
} catch (CancellationException e) {
cache.remove(arg);
} catch (ExecutionException e) {
throw launderThrowable(e.getCause());
}
}
}
}
java实现数据缓存的更多相关文章
- java——包装类数据缓存 ==号详解
Java对部分经常使用的数据采用缓存技术,即第一次使用该数据则创建该数据对象并对其进行缓存, 当再次使用等值对象时直接从缓存中获取,从而提高了程序执行性能.(只对常用数据进行缓存) Java中只是对部 ...
- java之ibatis数据缓存
使用IBatis作数据缓存 1.SqlMapConfig.xml中<settingscacheModelsEnabled="true" //设置为trueenhancemen ...
- Java EE数据持久化框架 • 【第5章 MyBatis代码生成器和缓存配置】
全部章节 >>>> 本章目录 5.1 配置MyBatis Generator 5.1.1 MyBatis Generator介绍 5.1.2 MyBatis Generat ...
- 「小程序JAVA实战」小程序数据缓存API(54)
转自:https://idig8.com/2018/09/22/xiaochengxujavashizhanxiaochengxushujuhuancunapi52/ 刚开始写小程序的时候,用户信息我 ...
- jQuery 2.0.3 源码分析 数据缓存
历史背景: jQuery从1.2.3版本引入数据缓存系统,主要的原因就是早期的事件系统 Dean Edwards 的 ddEvent.js代码 带来的问题: 没有一个系统的缓存机制,它把事件的回调都放 ...
- Android清除本地数据缓存代码案例
Android清除本地数据缓存代码案例 直接上代码: /* * 文 件 名: DataCleanManager.java * 描 述: 主要功能有清除内/外缓存,清除数据库,清除shar ...
- SpringMVC + ehcache( ehcache-spring-annotations)基于注解的服务器端数据缓存
背景 声明,如果你不关心java缓存解决方案的全貌,只是急着解决问题,请略过背景部分. 在互联网应用中,由于并发量比传统的企业级应用会高出很多,所以处理大并发的问题就显得尤为重要.在硬件资源一定的情况 ...
- mybatis存取blob对象+@Cacheable实现数据缓存
参考文档: http://www.ibm.com/developerworks/cn/opensource/os-cn-spring-cache/ 需求场景: 当前业务通过第三方接口查询一个业务数据, ...
- Eclipse rap 富客户端开发总结(14) :rap 图片、数据缓存处理
一.概述 在进行了 rap 的基本学习之后,您对 rap 的理解是否进入了更高的一个层次呢,个人觉得,对学习 rap 的人来说,进行 rap 的学习是一个探索的过程,在编程中不断的对其进行理解和分析, ...
随机推荐
- centos7/rhel7安装较高版本ruby2.2/2.3/2.4+
环境需求: 在Centos7.3中,通过yum安装ruby的版本是2.0.0,但是如果有些应用需要高版本的ruby环境,比如2.2,2.3,2.4...那就有点麻烦了,譬如:我准备使用redis官方给 ...
- 什么是Zookeeper?
https://mp.weixin.qq.com/s?__biz=MzAxOTc0NzExNg==&mid=2665514106&idx=1&sn=e54aae8cf68f5a ...
- logback 设置按天,文件切割大小,总共日志文件大小。
设置按天,文件切割大小,总共日志文件大小. <?xml version="1.0" encoding="UTF-8"?> <configura ...
- js jquery 数组的合并 对象的合并
转载自:http://www.cnblogs.com/xingxiangyi/p/6416468.html 1 数组合并 1.1 concat 方法 1 2 3 4 var a=[1,2,3],b=[ ...
- Vue -- 双向过滤器去除html标签
<div id="box"> <input type="text" v-model="msg | filterHtml"& ...
- 【洛谷P1226 【模板】快速幂||取余运算】
题目描述 输入b,p,k的值,求b^p mod k的值.其中b,p,k*k为长整型数. 输入输出格式 输入格式: 三个整数b,p,k. 输出格式: 输出“b^p mod k=s” s为运算结果 作为初 ...
- Win32-API: 终于能正常的捕获焦点事件: WM_COMMAND、BN_SETFOCUS、EN_SETFOCUS
学习和使用win32-api来写窗口程序算算也有二三年了. 以前有个需求,想捕获控件的焦点事件(SETFOCUS. KILLFOCUS),我一直认为应该捕获 WM_SETFOCUS 和 WM_KILL ...
- Spring2
简介:1.Aop编程.2.AspectJ基于xml文件.3.AspectJ基于注解. 4.JdbcTemplate. 5.配置properties文件 1 AOP 1.1 AOP介绍 ...
- tomcat部署-手动启动tomcat部署,添加网页,
公司的内网什么都不能往外传,于是自己用公司的网络搭了一个网页,在网上抄了一堆upload,用来来回传输数据.... 但是每次用ideaJ启动服务器太费时. 研究了一下怎么手动启动tomcat,部署网页 ...
- go框架gin的使用
我们在用http的时候一般都会用一些web框架来进行开发,gin就是这样的一个框架,它有哪些特点呢 一:gin特点 1.性能优秀2.基于官方的net/http的有限封装3.方便 灵活的中间件4.数据绑 ...