前言
在前面的文章ConcurrentHashMap分析(一)整体结构里,我们通过从 ConcurrentHashMap 的整体结构入手,逐步了解了它的数据结构和各个节点的转换关系。这篇文章将讲述 ConcurrenHashMap 的另一个重点:如何在高并发环境下进行扩容
,这对我们了解高并发编程思想很有帮助。
由于本人水平有限,分析过程中可能存在纰漏和错误,希望大家可以指出,一起学习,一起进步。
思路
我们在前文查看 ConcurrenHashMap 中发现有一个字段nextTable
,它起到在扩容时充当临时存储数组的作用:
/**
* The next table to use; non-null only while resizing.
*/
private transient volatile Node<K,V>[] nextTable;
既然 ConcurrentHashMap 是采用类似渐进式数据迁移的方式进行扩容,那么对于新旧数组来说,操作多线程就灵活很多了。我们接下来来看下它具体的扩容过程。
在之前分析 putVal
方法时,在后面有一个判断链表阈值的过程,如果链表长度超过一定阈值,就会触发转换为红黑树的操作,具体代码如下:
if (binCount != 0) {
if (binCount >= TREEIFY_THRESHOLD)
// 链表转换为红黑树
treeifyBin(tab, i);
if (oldVal != null)
return oldVal;
break;
}
进入treeifyBin
后,我们发现其实里面还有一个判断,如果当前存储 hash 槽的数组长度小于MIN_TREEIFY_CAPACITY(64)
时,那么只是对原有数组进行扩容2倍的操作。代码如下:
if ((n = tab.length) < MIN_TREEIFY_CAPACITY)
tryPresize(n << 1);
然后我们来分析tryPersize
方法:
private final void tryPresize(int size) {
// 将数组大小扩容为原来的2幂次
int c = (size >= (MAXIMUM_CAPACITY >>> 1)) ? MAXIMUM_CAPACITY :
tableSizeFor(size + (size >>> 1) + 1);
int sc;
while ((sc = sizeCtl) >= 0) {
Node<K,V>[] tab = table; int n;
// 如果原来的数组没有初始化
if (tab == null || (n = tab.length) == 0) {
n = (sc > c) ? sc : c;
// 开始初始化数组
if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
try {
// 再次判断数组是否需要初始化
if (table == tab) {
// 初始化数组
@SuppressWarnings("unchecked")
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
table = nt;
sc = n - (n >>> 2);
}
} finally {
sizeCtl = sc;
}
}
}
// 如果数组已经扩容过了或者容量大小已经大于 MAXIMUM_CAPACITY 了,就直接返回