ConcurrentHashMap中和HashMap一样,当容量不足时,需要进行扩容操作。由于ConcurrentHashMap需要支持并发下的扩容操作,因此要比HashMap复杂很多。下面是对扩容操作时,原table某处i位置上的链表从新分配到新table位置i和n+i部分的分析。
Node<K,V> ln, hn;
if (fh >= 0) {
// fh的第n位若为0,保持原位置fh & (n - 1) = i,对应链表ln,
// 否则移动到新位置fh & (n * 2 - 1) = n + i ,对应链表hn
int runBit = fh & n; // runBit为0或n(n是2的次幂,二进制只有一位为1)
// lastRun用来从原链表中寻找这样的结点:此结点以及之后的结点的hash
// 值,第n位要么全为0,要么全为1,这些结点构成的子链表(连续的,且在原链表末尾)可以原封不动的
// 放入到新table中i位置(hash值n位为0),或n+i位置(hash值n位为1)
Node<K,V> lastRun = f;
for (Node<K,V> p = f.next; p != null; p = p.next) { // 寻找这样的lastRun结点
int b = p.hash & n;
if (b != runBit) { // 反复的寻找,直到某个结点(lastRun)之后b和runBit一直都相等
runBit = b;
lastRun = p;
}
}
if (runBit == 0) { // 子链表中结点hash值第n位全为0,保存到ln中(i位置)
ln = lastRun;
hn = null;
}
else { // 子链表中结点hash值第n位全为1,保存到hn中(n+i位置)
hn = lastRun;
ln = null;
}
for (Node<K,V> p = f; p != lastRun; p = p.next) { // 将原链表中lastRun结点之前的部分分别加入ln和hn
int ph = p.hash; K pk = p.key; V pv = p.val;
// 从前往后遍历这些结点,不断加入到ln或hn的头部,可以看出
// 这些结点是被反序(相对原链表)加入到新链表中的
if ((ph & n) == 0)
ln = new Node<K,V>(ph, pk, pv, ln);
else
hn = new Node<K,V>(ph, pk, pv, hn);
}
setTabAt(nextTab, i, ln); // 将头结点ln放到新表中i位置
setTabAt(nextTab, i + n, hn); // 将头结点hn放到新表中i+n位置
// 该部分代码都是在原表i处链表头结点加锁的情况下运行的,释放锁钱,
// 将旧表的i位置设置为ForwardingNode结点表示此处已经修改完毕,其他线程不再修改
setTabAt(tab, i, fwd);
advance = true;
}