使用ConcurrentMap实现高效可靠的原子操作
问题:服务器S1从远程获取多个文件到本地处理。这些文件的数据会被Processor转换成不同类型的数据模型存放至S1的数据库。每个Processor处理逻辑是相互独立的,但是同一个文件的数据可能会被多个Processor访问。为了提高数据模型的转换效率,需要将文件进行缓存,已经被缓存的文件不能被重复缓存。问题代码如下:
[java] view plain copy
public class CacheManager
{
private Collection<String> cachedFiles = new ArrayList<>();
public void tryCache(String file)
{
if (!cachedFiles.contains(file))
{
doCache(file);
cachedFiles.add(file);
}
}
}
在多线程环境下,tryCache方法的逻辑会存在由“线程交织”所带来的文件被重复缓存的问题。
以下几种解决方法点评一下:
1 一种错误的方法
将cachedFiles字段使用诸如ConcurrentSkipListSet 或Collections.synchronizedSet,但是,该方法根本解决不了“线程交织”的问题。部分Java初学者容易犯这种错误。
2 一种不高效的方法
将tryCache声明为synchronized方法,或者在if (!cachedFiles.contains(file))语句块外用synchronized(cachedFiles),来实现互斥。这方法能保证if (!cachedFiles.contains(file))块在任何时候只能被一个线程执行,的确能避免文件被重复缓存。但是性能不高,例如,如果Processor1要缓存文件A,Processor2要缓存文件B,两者并不冲突,但是两个Processor只能串行通过tryCache,却不能同时进行。
3 一种高效,但不可靠的方法
使用ConcurrentMap的putIfAbsent实现高效的原子操作,但不可靠
经过改造的伪代码如下:
[java] view plain copy
public class CacheManager
{
private Collection<String> cachedFiles = new HashSet<>();
private ConcurrentMap<String, Long> cacheTimestamp = new ConcurrentHashMap<>();
public void tryCache(String file)
{
do
{
if (cacheTimestamp.putIfAbsent(file, System.currentTimeMillis()) == null)
{
if (!cachedFiles.contains(file))
{
doCache(file);
cachedFiles.add(file);
}
cacheTimestamp.remove(file);
break;
}
else
{
waitSomeTime();
}
}
while(!Thread.interrupted());
}
}
该方法能保证同一个file不会同时被多个Processor进行缓存,而且也能让处理不同file的Processor并发进行缓存。
但是该方法却并不可靠:如果Processor1在doCache(fileA)时发生异常导致cacheTimestamp.remove(fileA)不被执行,那么再也不会有其他Processor能通过cacheTimestamp.putIfAbsent(fileA, System.currentTimeMillis()) == null的校验,使得fileA永远不会再被缓存。cacheTimestamp也留下了一个垃圾记录。
4 一种高效,基本可靠的方法
[java] view plain copy
public class CacheManager
{
private Collection<String> cachedFiles = new HashSet<>();
private ConcurrentMap<String, Long> cacheTimestamp = new ConcurrentHashMap<>();
public void tryCache(String file)
{
do
{
if (cacheTimestamp.putIfAbsent(file, System.currentTimeMillis()) == null)
{
try
{
if (!cachedFiles.contains(file))
{
doCache(file);
cachedFiles.add(file);
}
break;
}
finally
{
cacheTimestamp.remove(file);
}
}
else
{
waitSomeTime();
}
}
while(!Thread.interrupted());
}
}
使用try ... finally ...来保证无论缓存成功与否,都能将cacheTimestamp中的记录清除。至此,这代码可以给个及格分了。什么,才及格?是的,请继续往下看
5 一种高效,靠超时机制来保证可靠性的方法
使用ConcurrentMap的putIfAbsent和replace方法,能实现上述问题的一种高效而可靠的解决方案。
[java] view plain copy
public class CacheManager
{
private Collection<String> cachedFiles = new HashSet<>();
private ConcurrentMap<String, Long> cacheTimestamp = new ConcurrentHashMap<>();
private final long TIMEOUT = 600000;
public void tryCache(String file)
{
do
{
Long timestamp = cacheTimestamp.putIfAbsent(file, System.currentTimeMillis() + TIMEOUT)
if (timestamp == null)
{
[java] view plain copy
timestamp = <span style="font-family: Arial, Helvetica, sans-serif;">cacheTimestamp.get(file);</span>
try
{
if (!cachedFiles.contains(file))
{
doCache(file);
cachedFiles.add(file);
}
break;
}
finally
{
cacheTimestamp.remove(file, <span style="font-family: Arial, Helvetica, sans-serif;">timestamp</span><span style="font-family: Arial, Helvetica, sans-serif;">); </span>
}
}
else if (System.currentTimeMillis() > timestamp) // 缓存file超时
{
if(cacheTimestamp.replace(file, timestamp, System.currentTimeMillis() + TIMEOUT))
{
try
{
if (!cachedFiles.contains(file))
{
doCache(file);
cachedFiles.add(file);
}
break;
}
finally
{
cacheTimestamp.remove(file, <span style="font-family: Arial, Helvetica, sans-serif;">timestamp</span><span style="font-family: Arial, Helvetica, sans-serif;">); </span>
}
}
}
else
{
wait(timestamp - System.currentTimeMillis());
}
}
while(!Thread.interrupted());
}
6 一种高效,靠等待机制来保证可靠性的方法
使用ConcurrentMap的putIfAbsent方法和CountDownLatch对象,能实现上述问题的另一种高效而可靠的解决方案。
[java] view plain copy
public class CacheManager
{
private Collection<String> cachedFiles = new HashSet<>();
private ConcurrentMap<String, CountDownLatch> cacheTimestamp = new ConcurrentHashMap<>();
public void tryCache(String file)
{
CountDownLatch signal = cacheTimestamp.putIfAbsent(file, new CountDownLatch(1))
if (signal == null)
{
signal = cacheTimestamp.get(file);
try
{
if (!cachedFiles.contains(file))
{
doCache(file);
cachedFiles.add(file);
}
break;
}
finally
{
signal.countDown();
cacheTimestamp.remove(file);
}
}
else
{
signal.await();
}
}
}

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
-
上一篇
Android ShareSDK桥接技术
ShareSDK本身基于android原生上集成使用的,但是为了能让广大开发者可以在各种引擎上使用ShareSDK,ShareSDK采用了跨语言的桥接技术,使得ShareSDK可以在游戏和网页引擎上使用,本票文章主要介绍下三种桥接技术的核心要点。我们知道,两种语言交互,相互调用、传参,接收执行结果等等动作,不外乎在于两个api,(1)主动去调用其它语言的某个方法;(2)接收方法执行的结果两种情况;方法执行的结果,主要又分成两种情况:一种是同步回调;一种是异步回调(也就是需要等待结果回来)。同步回调,也就是我们我们常用的return;异步回调,也就是平时所谓的各种监听,比如异步请求回调结果监听,或者常用的各种listener。比如使用A语言去调用B语言,通信结构图如下: OK,看到这个图是不是觉得非常简单,这些引擎,可能有些用户觉得这样是不是很麻烦,其实这些都有现成的api可以调用,现在来每个具体讲解下:一、Unity For ShareSDK:ShareSDK脚本开发语言主要是使用C#,直接步入正题,Unity是有一个java的支持包,叫做classes.jar,一般是在Unity安装...
-
下一篇
Developerkit & Link Develop Demo 环境配置指南
一、设备端开发 Visual Studio Code 本体:https://code.visualstudio.com/ 插件: C/C++ 和 alios-studio Python 2.7 下载地址:https://www.python.org/downloads/。 注意:macOS 及部分 Linux 系统已预装,Windows 及 部分 Linux 系统需要手动安装。 Windows 用户注意:安装 Python 时,务必选择 Add Python to environment variables 及 Install pip。 Git 下载地址:https://git-scm.com/downloads Windows 用户安装后,在任意文件夹右键快捷进入 Git bash,就可以使用 UNIX 终端指令,方便开发。 开发板 USB 驱动 下载
相关文章
文章评论
共有0条评论来说两句吧...