这天,笔者独自回忆着最近的面试过程,想起面试官问道:你一般用哪些Java集合呀?
我:🙋HashMap。
面试官:那你说说ConcurrentHashMap的扩容过程吧。
我:
最后面试官语重心长地说:年轻人要多看源码,搞懂底层逻辑,才能越走越远啊!
行吧,那今天就来好好看一看ConcurrentHashMap。
众所周知,JDK8中ConcurrentHashMap取消了Segment分段锁,采⽤CAS和synchronized来保证并发安全。数据结构跟 HashMap1.8的结构类似,采用数组+链表/红黑二叉树,即整个ConcurrentHashMap为一个Node[],Node[]的每一个元素均为首节点,首节点后面为链表或是红黑树。Java 8在链表⻓度超过⼀定阈值(8)时将链表(寻 址时间复杂度为O(N))转换为红黑树(寻址时间复杂度为O(log(N)))。
下面通过源码对JDK8的ConcurrentHashMap的get方法、put方法以及扩容过程进行分析。
get流程
public V get(Object key) {
Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
// spread 方法能确保返回结果是正数
int h = spread(key.hashCode());
if ((tab = table) != null && (n = tab.length) > 0 &&
(e = tabAt(tab, (n - 1) & h)) != null) {
// 如果头结点已经是要查找的 key
if ((eh = e.hash) == h) {
if ((ek = e.key) == key || (ek != null && key.equals(ek)))
return e.val;
}
// hash 为负数表示该 bin 在扩容中或是 treebin, 这时调用 find 方法来查找
else if (eh < 0)
return (p = e.find(h, key)) != null ? p.val : null;
// 正常遍历链表, 用 equals 比较
while ((e = e.next) != null) {
if (e.hash == h &&
((ek = e.key) == key || (ek != null && key.equals(ek))))
return e.val;
}
}
return null;
}
总体上看为无锁操作,流程为:
- 通过spread方法处理过的hash值找到Node[] tab的一个Node链表的头结点
- 如果该链表并不在扩容状态下且头结点就是要找的key,直接返回节点的val
- 如果该链表处于扩容状态中或是该链表之前已经被改造为红黑树,则调用find方法来进行查找
- 如果该链表既不在扩容状态下,同时头结点也不是要找的key,则遍历链表,最终找到目标节点并返回结点的val
put流程
public V put(K key, V value) {
return putVal(key, value, false);
}
final V putVal(K key, V value, boolean onlyIfAbsent) {
if (key == null || value == null) throw new NullPointerException();
// 其中 spread 方法会综合高位低位, 具有更好的 hash 性
int hash = spread(key.hashCode());
int binCount = 0;
for (Node<K,V>[] tab = table;;) {
// f 是链表头节点
// fh 是链表头结点的 hash
// i 是链表在 table 中的下标
Node<K,V> f; int n, i, fh; K fk; V fv;
// 要创建 table
if (tab == null || (n = tab.length) == 0)
// 初始化 table 使用了 cas, 无需 synchronized 创建成功, 进入下一轮循环
tab = initTable();
// 要创建链表头节点
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
// 添加链表头使用了 cas, 无需 synchronized
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 // check first node without acquiring lock
&& fh == hash
&& ((fk = f.key) == key || (fk != null