使用ConcurrentMap实现高效可靠的原子操作
2016-02-07 11:47
766 查看
问题:服务器S1从远程获取多个文件到本地处理。这些文件的数据会被Processor转换成不同类型的数据模型存放至S1的数据库。每个Processor处理逻辑是相互独立的,但是同一个文件的数据可能会被多个Processor访问。为了提高数据模型的转换效率,需要将文件进行缓存,已经被缓存的文件不能被重复缓存。问题代码如下:
在多线程环境下,tryCache方法的逻辑会存在由“线程交织”所带来的文件被重复缓存的问题。
以下几种解决方法点评一下:
1 一种错误的方法
将cachedFiles字段使用诸如ConcurrentSkipListSet 或Collections.synchronizedSet,但是,该方法根本解决不了“线程交织”的问题。部分Java初学者容易犯这种错误。
2 一种不高效的[b]方法[/b]
将tryCache声明为synchronized方法,或者在if (!cachedFiles.contains(file))语句块外用synchronized(cachedFiles),来实现互斥。这方法能保证if (!cachedFiles.contains(file))块在任何时候只能被一个线程执行,的确能避免文件被重复缓存。但是性能不高,例如,如果Processor1要缓存文件A,Processor2要缓存文件B,两者并不冲突,但是两个Processor只能串行通过tryCache,却不能同时进行。
3 一种高效,但不可靠的[b]方法[/b]
使用ConcurrentMap的putIfAbsent实现高效的原子操作,但不可靠
经过改造的伪代码如下:
该方法能保证同一个file不会同时被多个Processor进行缓存,而且也能让处理不同file的Processor并发进行缓存。
但是该方法却并不可靠:如果Processor1在doCache(fileA)时发生异常导致cacheTimestamp.remove(fileA)不被执行,那么再也不会有其他Processor能通过cacheTimestamp.putIfAbsent(fileA, System.currentTimeMillis()) == null的校验,使得fileA永远不会再被缓存。cacheTimestamp也留下了一个垃圾记录。
4 一种高效,基本可靠的方法
使用try ... finally ...来保证无论缓存成功与否,都能将cacheTimestamp中的记录清除。至此,这代码可以给个及格分了。什么,才及格?是的,请继续往下看
5 一种高效,靠超时机制来保证可靠性的方法
使用ConcurrentMap的putIfAbsent和replace方法,能实现上述问题的一种高效而可靠的解决方案。
6 一种高效,靠等待机制来保证可靠性的方法
使用ConcurrentMap的putIfAbsent方法和CountDownLatch对象,能实现上述问题的另一种高效而可靠的解决方案。
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 一种不高效的[b]方法[/b]
将tryCache声明为synchronized方法,或者在if (!cachedFiles.contains(file))语句块外用synchronized(cachedFiles),来实现互斥。这方法能保证if (!cachedFiles.contains(file))块在任何时候只能被一个线程执行,的确能避免文件被重复缓存。但是性能不高,例如,如果Processor1要缓存文件A,Processor2要缓存文件B,两者并不冲突,但是两个Processor只能串行通过tryCache,却不能同时进行。
3 一种高效,但不可靠的[b]方法[/b]
使用ConcurrentMap的putIfAbsent实现高效的原子操作,但不可靠
经过改造的伪代码如下:
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 一种高效,基本可靠的方法
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方法,能实现上述问题的一种高效而可靠的解决方案。
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) {
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对象,能实现上述问题的另一种高效而可靠的解决方案。
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(); } } }
相关文章推荐
- 搬迁Wordpress到wamp遇到的问题
- Linux---软件安装管理
- UVA11069 - A Graph Problem(DP)
- Yeoman快速搭建Web开发框架
- 递归(图的遍历)3
- 安装系统更新时,强制断电导致之后安装更新,提示错误的解决方案
- VB6.0中提示:该部件的许可证信息没有找到,在设计环境中,没有合适的许可证使用该功能”的解决办法
- 深入理解windows 消息机制
- 【Android开发小记--12】同步歌词
- 线程和QObjects
- 深入理解windows 消息机制
- bzoj4010【HNOI2015】菜肴制作
- Windows消息机制概述
- 从CODES 到ICCV
- Windows消息机制概述
- 蓝桥杯P1103 复数运算问题
- NoSQL之Redis(二)---Java操作Redis存储自定义类型数据
- yarn的工作流程
- cxf拦截器
- Head First C (后半部分)读书笔记