在日常开发中,资源池是经常遇到的场景,一种简单的实现是按需创建一个资源,然后放入map中缓存起来,后续使用这个资源时直接从map中获取.
最简单可靠的实现是利用HashedMap+synchronized(或者Lock)
这种方式无疑是正确的,但锁的粒度较大,高并发时性能不佳
改进的一种典型思路是利用JUC里的并发工具ConcurrentHashMap,降低锁粒度,提高并发性
http://dmy999.com/article/34/correct-use-of-concurrenthashmap里提到了一种实践,主要代码如下
private ConcurrentMap records = new ConcurrentHashMap();
private Record getOrCreate(String id) {
Record rec = records.get(id);
if (rec == null) {
// record does not yet exist
Record newRec = new Record(id);
rec = records.putIfAbsent(id, newRec);
if (rec == null) {
// put succeeded, use new value
rec = newRec;
}
}
return rec;
}
这种方法直接使用ConcurrentMap,且没有外层的锁,
putIfAbsent方法不会重复放入相同key的对象,
ConcurrentMap内部实现也保证了内存可见性.
看起来是基本正确的.而且大部分情况也是可以正常工作的.
但是这种写法有个致命的问题,那就是
Record newRec = new Record(id);
这个地方在并发情况下可能被多次执行.意味着资源被多次创建.
如果这个资源没有什么外部依赖,多次创建倒是没有问题,不过当遇到
依赖外部资源(文件,socket)时,多次创建就会有报错.
要避免这种资源的多次创建,可以使用double checked的方式实现.
虽然double checked由来已久,且有著名的double checked locking问题
关于此问题(请参见
http://en.wikipedia.org/wiki/Double-checked_locking
)的产生主要是java内存模型导致的,但是使用volatile可以避免lock的问题
可能早些时候很多著作里都会提到volatile代价太高,但随着jvm发展(1.5以后),volatile的实现
也变得轻量高效,具体的论述可以参见
http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html#dcl
注意页面里划掉的一大段论述
Double checked最早是为了解决单例模式产生的,目前java的单例模式已经有更好的
实现方式,比如static变量(dcl的wiki描述),比如enum单例(参见effective java 2nd)
但是在资源池场景仍然可以使用Double checked保证我们资源仅被创建一次
同时尽量降低同步的代价
代码如下
private ConcurrentMap records = new ConcurrentHashMap();
private Record getOrCreate(String id) {
Record rec = records.get(id);
if (rec == null) {
synchronized (this) {
rec = records.get(id);
if (rec == null) {
// record does not yet exist
Record newRec = new Record(id);
rec = records.putIfAbsent(id, newRec);
if (rec == null) {
// put succeeded, use new value
rec = newRec;
}
}
}
}
return rec;
}
注意此处使用了sychronized,仍然不能使用HashedMap代替ConcurrentHashMap
因为这种写法有多线程同时访问Map的get和put方法的可能,
而HashedMap的get和put方法并发环境下也是会出错的.
分享到:
相关推荐
ConcurrentHashMap使用了分段锁(Segment)来实现并发的读写操作,每个Segment都相当于一个小的HashMap,将整个哈希表分成多个部分。这样可以同时进行多个线程的并发读写操作,不会阻塞其他线程的访问。 需要注意的...
java源码剖析-ConcurrentHashMap
ConcurrentHashMap可以做到读取数据不加锁,并且其内部的结构可以让其在进行写操作的时候能够将锁的粒度保持地尽量地小,允许多个修改操作并发进行,其关键在于使用了锁分离技术。它使用了多个锁来控制对hash表的...
Java利用ConcurrentHashMap实现本地缓存demo; 基本功能有缓存有效期、缓存最大数、缓存存入记录、清理线程、过期算法删除缓存、LRU算法删除、获取缓存值等功能。 复制到本地项目的时候,记得改包路径哦~
ConcurrentHashMap源码剖析
源码分析见我博文:http://blog.csdn.net/wabiaozia/article/details/50684556
ConcurrentHashMap具体是怎么实现线程安全的呢,肯定不可能是每个方法加synchronized,那样就变成了HashTable。
这时候ConcurrentHashMap达到容量扩容而忽略了ReservationNode情况,调用put的时候在synchronized(f)没有对ReservationNode处理,所以会出现死循环。 在jdk1.8和1.9中对比 http://gee.cs.oswego.edu/cgi- bin/...
解析concurrenthashmap的源码,学习多线程的思想
程序员面试加薪必备_ConcurrentHashMap底层原理与源码分析深入详解
ConcurrentHashMap是J.U.C(java.util.concurrent包)的重要成员,它是HashMap的一个线程安全的、支持高效并发的版本。在默认理想状态下,ConcurrentHashMap可以支持16个线程执行并发写操作及任意数量线程的读操作。...
java本地缓存ConcurrentHashMap
ConcurrentHashMap之实现细节
concurrenthashmap1.7的源码分析
Java——并发容器之ConcurrentHashMap;Java——并发容器之ConcurrentHashMap;Java——并发容器之ConcurrentHashMap;Java——并发容器之ConcurrentHashMap;Java——并发容器之ConcurrentHashMap;Java——并发...
java7-8中的 HashMap和ConcurrentHashMap全解析
ConcurrentHashMap是一个线程安全的HashTable,它的主要功能是提供了一组和HashTable功能相同但是线程安全的方法。ConcurrentHashMap可以做到读取数据不加锁,并且其内部的结构可以让其在进行写操作的时候能够将锁的...
Java并发编程之ConcurrentHashMap Java并发编程之ConcurrentHashMap.pdf
ConcurrentHashMap的实现原理(JDK1.7和JDK1.8),并说明了在jdk1.7与jdk1.8的不同实现原理
ConcurrentHashMap的实现原理(纯干货)