简介
本文的主体内容,大概是从一个简单的需求
然后分析对应的并发问题,高效可伸缩,缓存污染问题,最后得到一个让我们满意的Map作为对应的实现.
最简单的实现
首先,先放一个最简单的实现.我相信,大部分的人(包括我)在内,在大部分情况就是这么实现的..
package current; import java.util.HashMap; public class Foo { private HashMap<String,String> dataCache = new HashMap<String, String>(); public String getData(String key){ String data = dataCache.get(key); if(data == null){ String newData = fetchData(key); dataCache.put(key,data); return newData; } else { return data; } } /** * 获取对应的数据 */ private String fetchData(String key){ return key + ":data"; } }
其实,这种实现基本上能满足没有高并发的大部分情况了..当然,作为码农,是绝对不会到此为止的..相信大部分的人也都发现了一个问题
解决的方法有两个,
2 把HashMap换成 ConcurrentHashMap .
一般来说,相信大家都会选择第二种,使用JDK自带的同步容器ConcurrentHashMap.
下面是改进以后的代码
package current; import java.util.concurrent.ConcurrentHashMap; public class Foo { private ConcurrentHashMap<String,String> dataCache = new ConcurrentHashMap<String, String>(); public String getData(String key){ String data = dataCache.get(key); if(data == null){ String newData = fetchData(key); dataCache.put(key,data); return newData; } else { return data; } } /** * 获取对应的数据 */ private String fetchData(String key){ return key + ":data"; } }
到此为止,难道结束了嘛..我们继续挑毛病,如果在高并发的情况下,可能对同一个key初始化两次(针对某一些缓存对象只能被初始化一次的情况,这种漏洞将会带来很大的安全风险). 针对这个问题,那么,可以引入一个闭锁实现 FutureTask
使用FutureTask来解决初始化两次的问题.
对应的代码如下
package current; import java.util.concurrent.*; public class Foo { private ConcurrentHashMap<String,FutureTask<String>> dataCache = new ConcurrentHashMap<String, FutureTask<String>>(); public String getData(final String key){ FutureTask<String> future = dataCache.get(key); if(future == null){ future = new FutureTask<String>(new Callable<String>() { @Override public String call() throws Exception { return fetchData(key); } }); dataCache.put(key,future); future.run(); } try { return future.get(); } catch (InterruptedException e) { //这里注意一下,引入futuretask造成了缓存污染,如果在future.run失败,需要在缓存中删除对应的记录 dataCache.remove(key); } catch (ExecutionException e) {} return null; } /** * 获取对应的数据 */ private String fetchData(String key){ return key + ":data"; } }
这个代码,看起来已经非常完美.如果其他线程看到前面的请求正在fetchData,那么也会等待run结束.但是,这还没结束.有一个地方没有做到同步
FutureTask<String> future = dataCache.get(key); if(future == null){ future = new FutureTask<String>(new Callable<String>() { @Override public String call() throws Exception { return fetchData(key); } }); dataCache.put(key,future);
高并发下 ,会出现,同一个key的future被创建两次,然后分别put覆盖到cache中.注意,这里的不同步和之前
String data = dataCache.get(key); if(data == null){ String newData = fetchData(key); dataCache.put(key,data);
这个代码的不同步是有非常本质的区别的.后者是等fetchData完成以后再put,而前者只是先往cache中注册一个future.然后马上就put了.两者的时间消耗完全不同.
为了解决上面的复合操作(其实就是put if absent),解决的方式就非常简单了,就是使用ConcurrentHashMap自带的putIfAbsent方法.
最终版本
package current; import java.util.concurrent.*; public class Foo { private ConcurrentHashMap<String,FutureTask<String>> dataCache = new ConcurrentHashMap<String, FutureTask<String>>(); public String getData(final String key){ FutureTask<String> future = dataCache.get(key); if(future == null){ future = new FutureTask<String>(new Callable<String>() { @Override public String call() throws Exception { return fetchData(key); } }); FutureTask old = dataCache.putIfAbsent(key,future); if(old == null){ future.run(); } else { future = old; } } try { return future.get(); } catch (InterruptedException e) { //这里注意一下,引入futuretask造成了缓存污染,如果在future.run失败,需要在缓存中删除对应的记录 dataCache.remove(key); } catch (ExecutionException e) {} return null; } /** * 获取对应的数据 */ private String fetchData(String key){ return key + ":data"; } }
貌似有些地方,还会在getData方法中加一下while(true)循环.如果fetchData出错,那就继续获取.这个就太霸气了点.我觉得这样已经差不多了..已经能满足99.99%的需求了
总结
其实,大部分情况,只是缓存一下数据,估计就做到ConcurrentHashMap那一层就差不多了.会引入future的非常少.这个就看自己看自己系统的情况吧.
相关推荐
spectrum-lsf-10.1.0-documentation.pdf
TTP229-LSF TonTouchTM IC是一款使用电容感应式原理设计的触摸芯片。此芯片内建稳压电路供触摸传感器使用,稳定的触摸效果可以应用在各种不同应用上,人体触摸面板可以通过非导电性绝缘材料连接,主要应用是以取代...
LSF-260型砂水分离器技术说明.pdf
从本质上讲,它要求您在拉取请求的注释中提供一个“签出”行,以表明该工作不影响其他人的工作。 同样,有关更多详细信息,请参见DCO自述文件。 发布信息 IBM Spectrum LSF Python包装器 支持LSF发行版:10.1 包装...
刃边法计算MTF(ESF、LSF、PSF) 光学镜头成像质量分析 参考文档
lsf-perl-api:LSF Perl模块用来操纵所有LSF的位置
matlab灰色处理代码LSF和MTF数码相机的测量 介绍 概括 在这项工作中,我尝试找到智能手机的线路扩展功能(LSF)和调制传递函数(MTF)...12.1.1中准备了一个测试图像(图1)。 所有对象均为实际大小。 此测试图像包括3
LSF-Kubernetes集成提供了三个关键功能: 在有限的资源供应范围内有效管理工作负载中高度可变的需求 在共享的多租户环境中为不同的使用者和工作负载提供改进的服务水平 优化了通用图形处理单元(GPGPU)等昂贵资源...
(Phys Rev D 85:114032,2012),及其对C = 1,S = -2和I = 0的影响,最近LHCb协作观察到了五个c(ˆ)状态 。 在模型中使用的介子-重子相互作用与手性和重夸克自旋对称性均一致,并导致对观测到的最低价奇数奇偶...
lsf操作手册 openlava亦可以使用之 详细的操作手册 lsf是IBM的一款集群调度软件 openlava是一款兼容lsf操作的集群调度软件
#lsf-stats lsf-stats使用带... 您不需要将它与 lsf-stats 放在同一台服务器上,甚至不需要放在同一个集群或子网上。 数据被推送到 plot.ly 的服务器。 当客户端访问网页时,他们会从 plot.ly 的服务器中拉取 - 而不
把MTVQ应用于MELP编码中,降低量化时的计算量,减少谱失真。
建立React材料UI Redux 草稿JS ChartJS 棱镜JS React降价React完整日历快速开始安装依赖项: npm install或yarn 启动服务器: npm run start或yarn start 视图处于打开状态: localhost:3000 推荐最新的...LSF-MSF-v1.0
超级计算cluster资源调度软件LSF管理员配置手册。
Laravel开发-lsf 只是一个简单的内腔Swool框架。
台湾通泰的触摸IC系列,用于8键16键触摸按键设计芯片
LSF参考手册,英文版,需要使用LSF的同行们可以拿来做查询工具
IBM Platform LSF产品系列是一款性能强大的工作负载管理平台,适用于要求苛刻的、分布式的,以及任务关键型高性能计算环境。它提供一整套全面的智能化、策略驱动的计划功能,可以帮助您利用所有计算基础架构资源,并...
IBM Platform LSF产品系列是针对高要求、分布式和关键任务 HPC 环境的一个强大的工作负载管理平台。它提供了一组广泛的智能、策略驱动的调度功能,使您能够充分利用您所有的计算基础架构资源并确保最佳应用性能。...
LSF使用方法、提交作业命令、LSF队列状况、查看作业状态和删除作业等