ConcurrentHashMap
ConcurrentHashMap 同样也分为 1.7 、1.8 版,两者在实现上略有不同。
1.7
/**
* Segment 数组,存放数据时首先需要定位到具体的 Segment 中。
*/
final Segment<K,V>[] segments;
transient Set<K> keySet;
transient Set<Map.Entry<K,V>> entrySet;
static final class Segment<K,V> extends ReentrantLock implements Serializable {
private static final long serialVersionUID = 2249069246763182397L;
// 和 HashMap 中的 HashEntry 作用一样,真正存放数据的桶
transient volatile HashEntry<K,V>[] table;
transient int count;
transient int modCount;
transient int threshold;
final float loadFactor;
}
其中HashEntry的组成和HashMap非常的类似,卫衣的区别就是其中的核心数据以及链表都是volatile修饰的,保证了获取时的可见性
原理上来说:ConcurrentHashMap 采用了分段锁技术,其中 Segment 继承于 ReentrantLock。不会像 HashTable 那样不管是 put 还是 get 操作都需要做同步处理,理论上 ConcurrentHashMap 支持 CurrencyLevel (Segment 数组数量)的线程并发。每当一个线程占用锁访问一个 Segment 时,不会影响到其他的 Segment。
1.8
1.7 已经解决了并发问题,并且能支持 N 个 Segment 这么多次数的并发,但依然存在 HashMap 在 1.7 版本中的问题。
查询遍历链表效率太低
其抛弃了 原有的 Segment 分段锁,而采用了 CAS + synchronized
来保证并发安全性。 也将 1.7 中存放数据的 HashEntry 改为 Node,但作用都是相同的。 其中的 val next
都用了 volatile 修饰,保证了可见性。
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
volatile V val;
volatile Node<K,V> next;
//..................
}
get方法
final V putVal(K key, V value, boolean onlyIfAbsent) {
if (key == null || value == null) throw new NullPointerException();
int hash = spread(key.hashCode());//计算hash值
int binCount = 0;
for (Node<K,V>[] tab = table;;) {//遍历node
Node<K,V> f; int n, i, fh;
if (tab == null || (n = tab.length) == 0)
tab = initTable();//初始化
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {//不含有key键的值
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null)))//cas存储键值对
break;
}
else if ((fh = f.hash) == MOVED)//判断是否需要扩容
tab = helpTransfer(tab, f);
else {
V oldVal = null;
//synchronized加锁
synchronized (f) {
if (tabAt(tab, i) == f) {//定位
if (fh >= 0) {
binCount = 1;//定义链表长度
for (Node<K,V> e = f;; ++binCount) {//遍历链表
K ek;
//如果key值相同且onlyIfAbsent==false则赋值
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;
//如果遍历到尾部,说明不存在此Key值,则进行添加node操作
if ((e = e.next) == null) {
pred.next = new Node<K,V>(hash, key,
value, null);
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;
}
}
}
}
//判断是否达到树形化条件
if (binCount != 0) {
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
if (oldVal != null)
return oldVal;
break;
}
}
}
addCount(1L, binCount);
return null;
}
1.8 在 1.7 的数据结构上做了大的改动,采用红黑树之后可以保证查询效率(
O(logn)
),甚至取消了 ReentrantLock 改为了 synchronized,这样可以看出在新版的 JDK 中对 synchronized 优化是很到位的。