简介
本文的主体内容,大概是从一个简单的需求
然后分析对应的并发问题,高效可伸缩,缓存污染问题,最后得到一个让我们满意的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的非常少.这个就看自己看自己系统的情况吧.
相关推荐
LSF 10.1.0 提供了一个快速入门指南,指导用户快速部署和使用 LSF。该指南涵盖了 LSF 的基本概念、安装和配置、作业调度和执行等方面的内容。 发行说明 LSF 10.1.0 的发行说明提供了对该版本的详细介绍,包括新增...
根据给定的文件信息,我们可以提取出关于IBM Spectrum LSF(前身为LSF,...而对于开发者和科研人员,LSF和OpenLava能够提供一个稳定、可伸缩的计算环境,从而专注于其研究和开发工作,无需担心底层计算资源的管理问题。
TTP229-LSF TonTouchTM IC是一款使用电容感应式原理设计的触摸芯片。此芯片内建稳压电路供触摸传感器使用,稳定的触摸效果可以应用在各种不同应用上,人体触摸面板可以通过非导电性绝缘材料连接,主要应用是以取代...
LSFProj.rar是一个压缩包,其中包含了与光学成像系统性能评估相关的文件,特别是关于“刃边MTF(Modulation Transfer Function)”的计算。刃边MTF是衡量光学系统分辨率的重要指标,它反映了系统传递图像细节的能力...
调制度是对比度的一个指标,与MTF直接相关,调制度越大,图像的对比度越高,成像效果越好。 MTF曲线展示了在不同空间频率下,系统保持图像对比度的能力。计算MTF通常是通过寻找线对中最大亮度点和最小亮度点的对比...
从本质上讲,它要求您在拉取请求的注释中提供一个“签出”行,以表明该工作不影响其他人的工作。 同样,有关更多详细信息,请参见DCO自述文件。 发布信息 IBM Spectrum LSF Python包装器 支持LSF发行版:10.1 包装...
每个节点代表一个子码本,从根节点到叶子节点的路径决定了最终的量化结果。这种结构不仅支持嵌入式量化,还允许编码器根据当前的比特预算动态调整量化精度,从而在不同质量等级间灵活切换。此外,MTVQ还具有较低的...
LSF-260型砂水分离器技术说明.pdf
LSF,全称为Load Sharing Facility,是Platform公司开发的一款分布式资源管理系统,主要用于集群环境中的任务调度、监控和负载分析。它的主要目标是对集群资源进行统一的管理和调度,确保高效利用计算资源。 ### ...
它通过一个集中式的调度器来统一管理集群中的计算资源,调度器根据预设的策略将计算任务分配给集群中的各个计算节点。同时,Platform LSF还提供了丰富的命令行工具和图形界面,方便用户提交、管理和监控作业。 在...
LSF Perl API则是一个专门为Perl编程语言设计的接口,允许开发者通过Perl脚本与LSF系统进行交互,实现对作业提交、监控、控制等一系列操作。 **Perl API的核心功能:** 1. **作业提交**:Perl API提供函数来创建并...
LSF基于集群架构运行,一个LSF集群通常包含多个节点,每个节点都可作为执行任务的计算资源。集群中的节点根据其功能可以分为管理节点和计算节点。管理节点负责接收、调度和监控作业,而计算节点则负责实际执行作业。...
TTP229-LSF包含了一个控制电路,该电路通过SDO、SCL和SDA等信号线与其他组件进行通信。其中: - **SDO (Serial Data Output)**:串行数据输出引脚。 - **SCL (Serial Clock Line)**:串行时钟线。 - **SDA (Serial ...
在IBM Spectrum LSF Version 10 Release 1.0中,"LSF License Scheduler" 是一个专门针对许可证管理的组件,确保在多用户环境下合理分配许可证资源。 **章节一:介绍** 1. **概述**:LSF License Scheduler 提供了...
(Phys Rev D 85:114032,2012),及其对C = 1,S = -2和I = 0的影响,最近LHCb协作观察到了五个c(ˆ)状态 。 在模型中使用的介子-重子相互作用与手性和重夸克自旋对称性均一致,并导致对观测到的最低价奇数奇偶...
matlab灰色处理代码LSF和MTF数码相机的测量 介绍 概括 在这项工作中,我尝试找到智能手机的线路扩展功能(LSF)和调制传递函数(MTF)...12.1.1中准备了一个测试图像(图1)。 所有对象均为实际大小。 此测试图像包括3
LSF-Kubernetes集成提供了三个关键功能: 在有限的资源供应范围内有效管理工作负载中高度可变的需求 在共享的多租户环境中为不同的使用者和工作负载提供改进的服务水平 优化了通用图形处理单元(GPGPU)等昂贵资源...
《HPC+LSF+LSF管理员手册》是针对IBM Spectrum LSF v10 Release 1的管理员指南,旨在帮助用户有效地管理和监控基于高性能计算(HPC)的集群环境。LSF(Load Sharing Facility)是一种分布式作业调度系统,用于优化...