concurrentHashMap是java.util.concurrent包下的一个类,该包中提供了非常多的容器,能够有效支持并发操作。而其中concurrentHashmap则是比较常用的一个集合,可以简单理解为一个线程安全的HashMap,至于concurrentHashmap与Hashtable的具体区别,我们在接下来说。
HashMap与ConcurrentHashMap
对于单线程程序来说,ConcurrentHashMap与HashMap使用起来没有区别,但是对于多线程,ConcurrentHashMap则是线程安全的,而Hashtable也是线程安全的,但是二者性能有很大差别,其原因在于底层结构的实现。
- Hashtable 对整个集合实现了一个锁,当一个线程访问时,其他线程不可进行访问
- ConcurrentHashMap 实现了分段锁,为集合中的每一段实现一个锁,当不同线程访问不同段时,不会出现阻塞情况
ConcurrentHashMap分段锁的实现
在HashMap中,底层结构使用为Entry数组+链表(JDK1.8之后优化为Entry数组+红黑树),而在ConcurrentHashMap中,Java为其增添了另一个数组Segment,每一个ConcurrentHashMap集合中有一定数量的segment(不可扩容,手动设置初始容量),而在每个segment数组中,又储存了一个Entry数组。我们为每一个segment配备一把锁,此时ConcurrentHashMap被多个segment分为多段,每段都有一把锁,当多线程访问时,只有多个线程访问同一段segment时,才会出现阻塞情况,否则会各自获取到所访问数据段的锁。
通过这种方法,有效提高了多线程访问效率,能够保证当访问数据在同一段时,可以不用阻塞等待,而实际上,我们也可以将ConcurrentHashMap看作为多个Hashtable的集合,每一个segment可以看为一个Hashtable,只有当访问同一个hashtable时,才会进行锁的竞争。如下图(图来源于网上,侵删)
可以看到,与之前HashMap最大区别即为多了一个Segment数组,我们源码分析就从segment数组开始
ConcurrentHashMap源码分析(JDK1.9)
首先我们找到源码中的Segment类(concurrentHashMap中主要为三个实体类,Node,segment和concurrentHashmap)
static class Segment<K,V> extends ReentrantLock implements Serializable {
private static final long serialVersionUID = 2249069246763182397L;
final float loadFactor;
Segment(float lf) { this.loadFactor = lf; }
}
可以看到,这里loadFactor为负载因子,在segment初始化时就自动完成,而在JDK1.8之后,取消了segment这个字段,改用HashEntry数组table来代替segment进一步减少并发冲突。
static final class MapEntry<K,V> implements Map.Entry<K,V> {
final K key; // non-null
V val; // non-null
final ConcurrentHashMap<K,V> map;
MapEntry(K key, V val, ConcurrentHashMap<K,V> map) {
this.key = key;
this.val = val;
this.map = map;
}
public K getKey() { return key; }
public V getValue() { return val; }
public int hashCode() { return key.hashCode() ^ val.hashCode(); }
public String toString() {
return Helpers.mapEntryToString(key, val);
}
public boolean equals(Object o) {
Object k, v; Map.Entry<?,?> e;
return ((o instanceof Map.Entry) &&
(k = (e = (Map.Entry<?,?>)o).getKey()) != null &&
(v = e.getValue()) != null &&
(k == key || k.equals(key)) &&
(v == val || v.equals(val)));
}
/**
* Sets our entry's value and writes through to the map. The
* value to return is somewhat arbitrary here. Since we do not
* necessarily track asynchronous changes, the most recent
* "previous" value could be different from what we return (or
* could even have been removed, in which case the put will
* re-establish). We do not and cannot guarantee more.
*/
public V setValue(V value) {
if (value == null) throw new NullPointerException();
V v = val;
val = value;
map.put(key, value);
return v;
}
}
可以看到,在HashMap内部中维护了一个concurrentHashMap的实例,当我们将Key,Value存入时,我们会将这个数据放入HashMap中,然后这个HashMap存入concurrentHashMap中,详见这里的构造函数。
Hash函数进行散列过程:首先进行一次散列,将数据均匀散列到table中,然后进行再哈希,进入hashEntry中。用Put来举例
public V put(K key, V value) {
return putVal(key, value, false);
}
/** Implementation for put and putIfAbsent */
final V putVal(K key, V value, boolean onlyIfAbsent) {
if (key == null || value == null) throw new NullPointerException();
int hash = spread(key.hashCode());
int binCount = 0;
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh; K fk; V fv;
if (tab == null || (n = tab.length) == 0)
tab = initTable();
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value)))
break; // no lock when adding to empty bin
}
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
else if (onlyIfAbsent && fh == hash && // check first node
((fk = f.key) == key || fk != null && key.equals(fk)) &&
(fv = f.val) != null)
return fv;
else {
V oldVal = null;
synchronized (f) {
if (tabAt(tab, i) == f) {
if (fh >= 0) {
binCount = 1;
for (Node<K,V> e = f;; ++binCount) {
K ek;
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
oldVal = e.val;
if (!onlyIfAbsent)
e.val = value;
break;
}
Node<K,V> pred = e;
if ((e = e.next) == null) {
pred.next = new Node<K,V>(hash, key, value);
break;
}
}
}
else if (f instanceof TreeBin) {
Node<K,V> p;
binCount = 2;
if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
value)) != null) {
oldVal = p.val;
if (!onlyIfAbsent)
p.val = value;
}
}
else if (f instanceof ReservationNode)
throw new IllegalStateException("Recursive update");
}
}
if (binCount != 0) {
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
if (oldVal != null)
return oldVal;
break;
}
}
}
addCount(1L, binCount);
return null;
}
过程大致为,先对键值对进行判断,然后将键值对储存如一个table中,然后将table放入数组,之后进行一些散列处理。