目录
1.类图对比HashMap
数据结构与HashMap类似,详情请戳,在HashMap的基础上在其API接口上进行了并发访问控制等实现,使其变得线程安全。
ConcurrentHashMap(CHM) | HashMap(HM) |
---|---|
2.并发实现
2.1 内部对象安全发布
数据结构与HashMap对比可以发现table是大体相似的,而entrySet等返回的都是View的视图包装类。
防止直接发布内部容器,被意外修改,导致Map的不变式约束被破坏,数据不一致不完整。
读写操作都做了重写,当对entrySetView等进行修改时,会同步修改this map,不会存在意外修改导致的数据不一致状况。具体请见下节
2.2 并发读写
2.2.1 get读
由于是读操作,实现的最简单,通过volatile语义保证桶里头元素的可见性,但是如果后面的链被修改可能不是最新的值。
/*
* 大部分与HashMap实现相似
* 重点是tabAt和e.find 方法
*/
public V get(Object key) {
Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
int h = spread(key.hashCode());
if ((tab = table) != null && (n = tab.length) > 0 && (e = tabAt(tab, (n - 1) & h)) != null) {//找到对应桶下标对应第一个Node
if ((eh = e.hash) == h) {
if ((ek = e.key) == key || (ek != null && key.equals(ek))) //头上正好命中
return e.val;
} else if (eh < 0)
return (p = e.find(h, key)) != null ? p.val : null; // 链表节点或者树节点查找
while ((e = e.next) != null) {// 遍历链表
if (e.hash == h && ((ek = e.key) == key || (ek != null && key.equals(ek))))
return e.val;
}
}
return null;
}
其中有个很重要的方法tabAt方法,通过Unsafe U去的getObjectVolatile方法,volatile语义保证可见性和有序性!
//读取 sun.misc.Unsafe U,单例,通过JNI调用
static final <K,V> Node<K,V> tabAt(Node<K,V>[] tab, int i) {
return (Node<K,V>)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE);
}
2.2.2 put
以下是put的核心方法,比HashMap增加了并发控制:
- 无锁CAS:casTabAt通过Unsafe U的CAS(无锁化)操作插入到空桶,负载较小且散列度较高的情况下极大提升效率(比互斥锁)
- volatile:轻量级锁保证可见性同时提升并发
- 锁细化:不是空桶的情况下,使用桶里的头元素的内部monitor锁(synchronized)保护桶后面链着的整个链表或者红黑树!比起锁住整个map,仅锁住一个桶,锁粒度大大减小!,提升了并发度。
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;
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, null)))
break; // 空桶无锁CAS技术抢头位置
}
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
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; // key同,覆盖
break;
}
Node<K,V> pred = e;
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;
}
//CAS替换
static final <K,V> boolean casTabAt(Node<K,V>[] tab, int i, Node<K,V> c, Node<K,V> v) {
return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v);
}
// set 写入
static final <K,V> void setTabAt(Node<K,V>[] tab, int i, Node<K,V> v) {
U.putObjectVolatile(tab, ((long)i << ASHIFT) + ABASE, v);
}
Unsafe采用单例模式,是JDK实现的底层核心类(rt.jar),其方法通过JNI直接调用操作系统接口操作内存。