上一篇ConcurrentHashMap源码解析中只是初步分析了ConcurrentHashMap的实现,这篇将详细讲述ConcurrentHashMap的扩容实现过程。
触发扩容的情况
当往ConcurrentHashMap中插入一个Key/Value结点时,有可能触发扩容。
1.第一种情况; 如果新增结点后,所在链表的元素大于等于阈值8,则会调用treeifyBin()方法,把链表转化为红黑树,但是再进行结构转换之前,还需要对数组长度进行判断。
treeifyBin()方法:
private final void treeifyBin(Node<K,V>[] tab, int index) {
Node<K,V> b; int n, sc;
if (tab != null) {
if ((n = tab.length) < MIN_TREEIFY_CAPACITY)
tryPresize(n << 1);
else if ((b = tabAt(tab, index)) != null && b.hash >= 0) {
synchronized (b) {
if (tabAt(tab, index) == b) {
TreeNode<K,V> hd = null, tl = null;
for (Node<K,V> e = b; e != null; e = e.next) {
TreeNode<K,V> p =
new TreeNode<K,V>(e.hash, e.key, e.val,
null, null);
if ((p.prev = tl) == null)
hd = p;
else
tl.next = p;
tl = p;
}
setTabAt(tab, index, new TreeBin<K,V>(hd));
}
}
}
}
}
treeifyBin()方法中有判断:
if ((n = tab.length) < MIN_TREEIFY_CAPACITY)
tryPresize(n << 1);
如果数组长度n小于阈值MIN_TREEIFY_CAPACITY,则会调用tryPresize方法把数组长度扩大到原来的两倍。最终触发transfer()方法。
- 第二种情况;新增结点之后,putVal()方法中会调用addCount方法记录元素个数。并检查是否进行扩容,当元素个数达到阈值时,触发transfer()方法。
上述两种情况都最终提到了transfer()方法(transfer方法会重新调整结点位置),transfer()方法是真正意义上的扩容操作。
transfer()方法的实现
private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
int n = tab.length, stride;
if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE) //每个线程处理桶的最小数目,可以看出核数越高步长越小,最小16个。
stride = MIN_TRANSFER_STRIDE; // subdivide range
if (nextTab == null) {
try {
@SuppressWarnings("unchecked")
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1]; //扩容到2倍
nextTab = nt;
} catch (Throwable ex) {
// try to cope with OOME
sizeCtl = Integer.MAX_VALUE; //扩容保护
return;
}
nextTable = nextTab;
transferIndex = n; //扩容总进度,>=transferIndex的桶都已分配出去。
}
int nextn = nextTab.length;
//扩容时的特殊节点,标明此节点正在进行迁移,扩容期间的元素查找要调用其find()方法在nextTable中查找元素。
ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);
//当前线程是否需要继续寻找下一个可处理的节点
boolean advance = true;
boolean finishing = false; //所有桶是否都已迁移完成。
for (int i =