# 内容目录

### 深度分析ConcurrentHashMap中的并发扩容机制

ConcurrentHashMap中扩容部分的设计非常巧妙，它通过使用CAS机制实现无锁的并发同步策略，同时对于同步锁synchronized，也只把粒度控制到了单个数据节点做数据迁移的这个范围，并且利用多个线程来进行并行扩容，大大提高了数据迁移的效率。

#### 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)
stride = MIN_TRANSFER_STRIDE; // subdivide range
if (nextTab == null) {            // initiating
try {
@SuppressWarnings("unchecked")
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1];
nextTab = nt;
} catch (Throwable ex) {      // try to cope with OOME
sizeCtl = Integer.MAX_VALUE;
return;
}
nextTable = nextTab;
transferIndex = n;
}
int nextn = nextTab.length;
ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);
boolean finishing = false; // to ensure sweep before committing nextTab
for (int i = 0, bound = 0;;) {
Node<K,V> f; int fh;
int nextIndex, nextBound;
if (--i >= bound || finishing)
else if ((nextIndex = transferIndex) <= 0) {
i = -1;
}
else if (U.compareAndSwapInt
(this, TRANSFERINDEX, nextIndex,
nextBound = (nextIndex > stride ?
nextIndex - stride : 0))) {
bound = nextBound;
i = nextIndex - 1;
}
}
if (i < 0 || i >= n || i + n >= nextn) {
int sc;
if (finishing) {
nextTable = null;
table = nextTab;
sizeCtl = (n << 1) - (n >>> 1);
return;
}
if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {
if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)
return;
i = n; // recheck before commit
}
}
else if ((f = tabAt(tab, i)) == null)
advance = casTabAt(tab, i, null, fwd);
else if ((fh = f.hash) == MOVED)
else {
synchronized (f) {
if (tabAt(tab, i) == f) {
Node<K,V> ln, hn;
if (fh >= 0) {
int runBit = fh & n;
Node<K,V> lastRun = f;
for (Node<K,V> p = f.next; p != null; p = p.next) {
int b = p.hash & n;
if (b != runBit) {
runBit = b;
lastRun = p;
}
}
if (runBit == 0) {
ln = lastRun;
hn = null;
}
else {
hn = lastRun;
ln = null;
}
for (Node<K,V> p = f; p != lastRun; p = p.next) {
int ph = p.hash; K pk = p.key; V pv = p.val;
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);
setTabAt(nextTab, i + n, hn);
setTabAt(tab, i, fwd);
}
else if (f instanceof TreeBin) {
TreeBin<K,V> t = (TreeBin<K,V>)f;
TreeNode<K,V> lo = null, loTail = null;
TreeNode<K,V> hi = null, hiTail = null;
int lc = 0, hc = 0;
for (Node<K,V> e = t.first; e != null; e = e.next) {
int h = e.hash;
TreeNode<K,V> p = new TreeNode<K,V>
(h, e.key, e.val, null, null);
if ((h & n) == 0) {
if ((p.prev = loTail) == null)
lo = p;
else
loTail.next = p;
loTail = p;
++lc;
}
else {
if ((p.prev = hiTail) == null)
hi = p;
else
hiTail.next = p;
hiTail = p;
++hc;
}
}
ln = (lc <= UNTREEIFY_THRESHOLD) ? untreeify(lo) :
(hc != 0) ? new TreeBin<K,V>(lo) : t;
hn = (hc <= UNTREEIFY_THRESHOLD) ? untreeify(hi) :
(lc != 0) ? new TreeBin<K,V>(hi) : t;
setTabAt(nextTab, i, ln);
setTabAt(nextTab, i + n, hn);
setTabAt(tab, i, fwd);
}
}
}
}
}
}``````

##### 第一个部分，创建扩容后的数组

• 计算每个线程处理的区间大小，默认是16。`(NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE`这段代码的目的是让每个CPU处理的数据区间大小相同，避免出现数据转移任务分配不均匀的现象。如果数组的长度比较小的话，默认一个CPU处理的长度是16。
• 初始化一个新的数组`nt`赋值给`nextTab`，该数组的长度是原来长度的`n << 1`，并且初始化一个`transferIndex`，默认值为老的数组长度。
``````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)
stride = MIN_TRANSFER_STRIDE; // subdivide range
if (nextTab == null) {            // initiating
try {
@SuppressWarnings("unchecked")
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1];
nextTab = nt;
} catch (Throwable ex) {      // try to cope with OOME
sizeCtl = Integer.MAX_VALUE;
return;
}
nextTable = nextTab;
transferIndex = n;
}
}``````
##### 第二个部分，数据迁移区间计算

``````private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
//省略部分代码....
int nextn = nextTab.length;
ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);
boolean finishing = false; // to ensure sweep before committing nextTab
for (int i = 0, bound = 0;;) {
Node<K,V> f; int fh;
int nextIndex, nextBound;
if (--i >= bound || finishing)
else if ((nextIndex = transferIndex) <= 0) {
i = -1;
}
else if (U.compareAndSwapInt
(this, TRANSFERINDEX, nextIndex,
nextBound = (nextIndex > stride ?
nextIndex - stride : 0))) {
bound = nextBound;
i = nextIndex - 1;
}
}
//省略部分代码....
}
}``````

• `ForwardingNode`这个表示一个正在被迁移的Node，当原数组中位置`x`节点的数据完成迁移后，会对`x`位置设置一个`ForwardingNode`表示该位置已经处理过了。
• `advance`字段是用来判断是否还有待处理的数据迁移工作。
• `while`循环中的方法就是用来计算区间，假设当前数组长度是32位，需要扩容到64位，此时`transferIndex=32``nextn=64`` n=32`

• 第一次循环，`i=0``nextIndex=32`。进入到`U.CompareAndSwapInt`方法，修改`transferIndex`的值，如果`transferIndex==nextIndex`， 则把transferIndex修改为`16``nextBound=16`. 此时`bound=16. i=31`，当前线程负责迁移的数组区间为`[16,31]`
• 第二次循环，`--i=30``nextIndex=16``transferIndex=16`，进入到`U.compareAndSwapIndex`，修改`transferIndex`的值为`0``nextBound=0``bound=0``i=nextIndex-1=15`，当前线程负责迁移的数组区间为[0,15]。

每次循环，都是通过`if (--i >= bound || finishing)`来判断数组区间是否分配完成，也就是说，数组从高往低进行迁移，比如第一次循环，处理的区间是[16,31]， 那么就会从31位开始往前进行遍历，对每个链表进行数据转移。

##### 第三个部分，更新扩容标记

• 如果`i`所在位置的Node为空，说明当前没有数据，不需要迁移，直接通过`casTabAt`修改成`fwd`占位即可。
• 如果`i`位置所在的Node数据的hash值为`MOVED`，说明当前节点已经被迁移过了，继续往下遍历。
``````private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
//省略部分代码....
else if ((f = tabAt(tab, i)) == null)
advance = casTabAt(tab, i, null, fwd);
else if ((fh = f.hash) == MOVED)
//省略部分代码....
}``````
##### 第四个部分，开始数据迁移和扩容

• 首先对当前要迁移的节点`f`增加同步锁synchronized，避免多线程竞争。
• `fh>=0`表示`f`节点为链表或者普通节点，则按照链表或者普通节点的方式来进行数据迁移。
• `f instanceof TreeBin`表示`f`节点为红黑树，按照红黑树的规则进行数据迁移，这里需要注意的是，数据迁移之后可能会存在红黑树转化成链表的情况，就是当链表长度小于等于6的时候，就会转化为链表。
``````private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
//省略部分代码....
for (int i = 0, bound = 0;;) {
//省略部分代码....
synchronized (f) {
if (tabAt(tab, i) == f) {
Node<K,V> ln, hn;
if (fh >= 0) {
int runBit = fh & n;
Node<K,V> lastRun = f;
for (Node<K,V> p = f.next; p != null; p = p.next) {
int b = p.hash & n;
if (b != runBit) {
runBit = b;
lastRun = p;
}
}
if (runBit == 0) {
ln = lastRun;
hn = null;
}
else {
hn = lastRun;
ln = null;
}
for (Node<K,V> p = f; p != lastRun; p = p.next) {
int ph = p.hash; K pk = p.key; V pv = p.val;
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);
setTabAt(nextTab, i + n, hn);
setTabAt(tab, i, fwd);
}
else if (f instanceof TreeBin) {
//如果当前节点是红黑树，则按照红黑树的处理逻辑进行迁移。
}
}
}
}
}``````

``````final V putVal(K key, V value, boolean onlyIfAbsent) {
//省略代码....
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;                   // no lock when adding to empty bin
}
//省略代码....
}
//省略代码....
}``````

``````if (tabAt(tab, i) == f) {
Node<K,V> ln, hn;
if (fh >= 0) {
int runBit = fh & n;
Node<K,V> lastRun = f;
for (Node<K,V> p = f.next; p != null; p = p.next) {
int b = p.hash & n;
if (b != runBit) {
runBit = b;
lastRun = p;
}
}
if (runBit == 0) {
ln = lastRun;
hn = null;
}
else {
hn = lastRun;
ln = null;
}
for (Node<K,V> p = f; p != lastRun; p = p.next) {
int ph = p.hash; K pk = p.key; V pv = p.val;
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);
setTabAt(nextTab, i + n, hn);
setTabAt(tab, i, fwd);
}``````

• 通过for循环遍历当前节点链表计算出当前链表最后一个需要迁移或者不需要迁移的节点位置。遍历每一个节点通过`p.hash&n`计算一个值，这个值有两个结果，一个是等于0，表示需要迁移的数据，一个是大于0，表示不需要迁移的数据。

``````for (Node<K,V> p = f.next; p != null; p = p.next) {
int b = p.hash & n;
if (b != runBit) {
runBit = b;
lastRun = p;
}
}``````

为了更好的帮助大家理解，我把前面的那个链表通过上面的计算用图形的方式表达如下，`runBit`针对头部节点计算得到的值是0，根据不断循环计算最终找到最后高位或者低位的位置所在的节点是100

需要注意，这里说的最后一位，不是指真正意义上的最后一位，而是指节点中后续不存在高低位变化的节点的最早一个节点。假设在下图中100这个节点后面还存在runBit=0的节点，此时返回的lastRun仍然是100对应的节点。之所以这么设计是因为后续如果不存在需要迁移的节点时，那么它本身就是一个链，不需要再次遍历处理，减少遍历次数。

• 通过`runBit`进行判断，当前链表中最后一个节点是属于高位还是低位，如果`runBit==0`表示低位，则把`lastRun`赋值给`ln`低位链。否则，赋值给`hn`高位链。

``````if (runBit == 0) {
ln = lastRun;
hn = null;
}
else {
hn = lastRun;
ln = null;
}``````

此时， ln=lastRun=hash值100对应的节点，hn=null。

• 再一次遍历整个链表，把原本的链表构建出高低链。

``````for (Node<K,V> p = f; p != lastRun; p = p.next) {
int ph = p.hash; K pk = p.key; V pv = p.val;
if ((ph & n) == 0)
ln = new Node<K,V>(ph, pk, pv, ln);
else
hn = new Node<K,V>(ph, pk, pv, hn);
}``````

通过上述代码执行之后，高低位拆分情况如下图所示。

• 最后，把低位链设置到扩容后的数组`i`位置，高位链设置到`i+n`的位置。

``````setTabAt(nextTab, i, ln);
setTabAt(nextTab, i + n, hn);
setTabAt(tab, i, fwd);

##### 第五部分，完成迁移后的判断

``````if (i < 0 || i >= n || i + n >= nextn) {
int sc;
if (finishing) {
nextTable = null;
table = nextTab;
sizeCtl = (n << 1) - (n >>> 1);
return;
}
if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {
if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)
return;
i = n; // recheck before commit
}
}``````

• 如果数据迁移工作完成，则把扩容后的数组赋值给`table`
• 如果还未完成，说明还有其他线程正在执行中，所以当前线程通过`U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)`修改并发扩容的线程数量（这部分代码在前面章节中分析过了，sizeCtl低16位会记录并发扩容线程数量），如果`(sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT`满足，说明没有线程在协助扩容，也就是说扩容结束了。

##### 跟着Mic学架构

《Spring Cloud Alibaba 微服务原理与实战》、《Java并发编程深度理解及实战》作者。 咕泡教育联合创始...

316 声望
799 粉丝
0 条评论