并发编程中经常用到的ConcurrentHashMap

Posted 每日运维

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了并发编程中经常用到的ConcurrentHashMap相关的知识,希望对你有一定的参考价值。

今天在并发编程中涉及到重复的查找sql,一次任务几十万次重复查找sql,而且结果有些是重复的,当时就想到了使用ConcurrentHashMap做本地缓存,逻辑很简单,就是用时先查缓存,没有时更新缓存,从缓存中拿结果。

ConcurrentHashMap实现原理

首先,ConcurrentHashMap的目标是实现支持高并发、高吞吐量的线程安全的HashMap。

其次,ConcurrentHashMap引入了分割,一个ConcurrentHashMap由多个segment组成,每一个segment都包含了一个HashEntry数组的hashtable,并提供了HashTable支持的所有的功能。支持多线程对Map做读操作,并且不需要任何的blocking。ConcurrentHashMap还使用了ReentrantLock来对segments加锁,每一个segment包含了对自己的hashtable的操作,比如get,put,replace等操作,这些操作发生的时候,对自己的hashtable进行锁定。由于每一个segment写操作只锁定自己的hashtable,所以可能存在多个线程同时写的情况,
性能无疑好于只有一个hashtable锁定的情况。ConcurrentHashMap默认的并发级别是16。

具体理解Segment数组的意义就是将一个大的table分割成多个小的table来进行加锁,也就是上面的提到的锁分离技术,而每一个Segment元素存储的是HashEntry数组+链表,这个和HashMap的数据存储结构一样。
    
JDK6,7中的ConcurrentHashmap主要使用Segment来实现减小锁粒度,把HashMap分割成若干个Segment,在put的时候需要锁住Segment,get时候不加锁,使用volatile来保证可见性,当要统计全局时(比如size),首先会尝试多次计算modcount来确定,这几次尝试中,是否有其他线程进行了修改操作,如果没有,则直接返回size。如果有,则需要依次锁住所有的Segment来计算。

源码分析 在ConcurrentHashMap的remove,put操作还是比较简单的,都是将remove或者put操作交给key所对应的segment去做的,所以当几个操作不在同一个segment的时候就可以并发的进行。

public V remove(Object key) {
    int hash = hash(key.hashCode());
        return segmentFor(hash).remove(key, hash, null);
    }

public V get(Object key) {
        int hash = hash(key.hashCode());
        return segmentFor(hash).get(key, hash);
    }    
    
putIfAbsent(key,value) //在元素不存在时插入元素

什么时候使用ConcurrentHashMap:
1.ConcurrentHashMap适用于读者数量超过写者时,因为在读取的大多数时候都没有用到锁定,所以读取操作几乎是完全的并发操作.
2.ConcurrentHashMap适用于做cache,在程序启动时初始化,之后可以被多个请求线程访问。记着定期清除缓存。

最后,ConcurrentHashMap关键点:
ConcurrentHashMap允许并发的读和线程安全的更新操作
在执行写操作时,ConcurrentHashMap只锁住部分的Map
并发的更新是通过内部根据并发级别将Map分割成小部分实现的
高的并发级别会造成时间和空间的浪费,低的并发级别在写线程多时会引起线程间的竞争
ConcurrentHashMap的所有操作都是线程安全
ConcurrentHashMap返回的迭代器是弱一致性,fail-safe并且不会抛出ConcurrentModificationException异常
ConcurrentHashMap不允许null的键值
可以使用ConcurrentHashMap代替HashTable,但要记住ConcurrentHashMap不会锁住整个Map
        
简单小例子:
ConcurrentHashMap<String, String> cacheMap = new ConcurrentHashMap<>();
//从缓存中获取
v = getCacheKey(k);

if (cacheMap.containsKey(k)) {
    return cacheMap.get(k);
}
// 如果缓存中没有,把该对象初始化缓存到concurrentHashMap中
    initCache(k);
    return cacheMap.get(k);
}    

cacheMap.clear();

以上是关于并发编程中经常用到的ConcurrentHashMap的主要内容,如果未能解决你的问题,请参考以下文章

eclipse中经常用到的修改菜单项

并发编程并发编程中你需要知道的基础概念

并发编程基础之wait以及notify的用法

Javascript中经常用到的

运维工作中经常用到的一些知识总结

开发中经常用到的特效效果