导致扩容的情况
在了解JDK1.8的ConcurrentHashMap扩容机制之前,要先知道ConcurrentHashMap什么情况会导致扩容。
1.put操作(插入键值对)
put函数的操作要通过putVal操作,如果有特殊情况要扩容。
put操作代码:

1 public V put(K key, V value) { 2 return putVal(key, value, false); 3 }
putVal代码(注释感谢简书作者代码potty):

1 //onlyIfAbsent跟HashMap一样,就是判断是否要覆盖,默认为false,覆盖 2 final V putVal(K key, V value, boolean onlyIfAbsent) { 3 if (key == null || value == null) throw new NullPointerException(); 4 int hash = spread(key.hashCode()); 5 //binCount=0说明首节点插入,未进行链表或红黑树操作,因为后面会对这个值进行更改 6 int binCount = 0; 7 for (Node<K,V>[] tab = table;;) { 8 Node<K,V> f; int n, i, fh; 9 //如果数组为空或者长度为0,进行初始化工作 10 if (tab == null || (n = tab.length) == 0) 11 tab = initTable(); 12 //如果获取位置的节点为空,说明是首节点插入情况 13 else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) { 14 if (casTabAt(tab, i, null,new Node<K,V>(hash, key, value, null))) 15 break;//直接在首节点插入对应元素,不用加锁 16 } 17 //如果hash值等于MOVEN(默认-1),说明是协助扩容,与transfer里面的ForwardingNode类有关 18 else if ((fh = f.hash) == MOVED) 19 tab = helpTransfer(tab, f);//协助扩容,这里就是用到transfer函数的地方 20 else { 21 V oldVal = null; 22 //对桶的首节点进行加锁 23 synchronized (f) { 24 //双重判定,为了防止在当前线程进来之前,i地址所对应对象已经更改 25 if (tabAt(tab, i) == f) { 26 //TreeBin类型的hash值默认设置为了-2 27 if (fh >= 0) { 28 binCount = 1; 29 for (Node<K,V> e = f;; ++binCount) { 30 K ek; 31 //在当前桶中找到位置跳出 32 if (e.hash == hash && 33 ((ek = e.key) == key || 34 (ek != null && key.equals(ek)))) { 35 oldVal = e.val; 36 if (!onlyIfAbsent) 37 e.val = value; 38 break; 39 } 40 Node<K,V> pred = e; 41 //当到桶的结尾还没找到,则新增一个Node 42 if ((e = e.next) == null) { 43 pred.next = new Node<K,V>(hash, key, 44 value, null); 45 break; 46 } 47 } 48 } 49 //如果hash小于0,判断是否是TreeBin的实例 50 else if (f instanceof TreeBin) { 51 Node<K,V> p; 52 binCount = 2; 53 if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key, 54 value)) != null) { 55 oldVal = p.val; 56 if (!onlyIfAbsent) 57 p.val = value; 58 } 59 } 60 } 61 } 62 //如果binCount值不等于0,说明进行了链表或者红黑树操作 63 if (binCount != 0) { 64 //如果binCount大于8则进行树化,但真正的转换成红黑树不是8的长度 65 //当长度超过64才会真正的树化,处于8-64之间的还只是数组扩容 66 if (binCount >= TREEIFY_THRESHOLD) 67 //这个就是树化链表操作 68 treeifyBin(tab, i); 69 if (oldVal != null) 70 return oldVal; 71 break; 72 } 73 } 74 } 75 //计数方法 76 addCount(1L, binCount); 77 return null; 78 }
插入1对键值对时,先判断数组是否空,长度是否为0(没有就先创建1个数组),再判断数组对应表是否为空节点(没有表直接现创一个,完成后退出函数)
这个节点的hash是-1的情况(MOVEN)下,需要先进行协助扩容,再进行下一步操作
之后,在这个表上执行插入操作,如果插入之后,这个表的长度超过8,会进行下一步处理:
表数据量在8-64之间时,会优先扩容hash数组;只有表数据量超过64时,内部才会执行树化。
(防止hash表的查询时间复杂度从O(1)过快退化成O(lg n),如果不重写类比较器Compare,查询复杂度会更进一步退化为O(n))
/**********************其他函数作用的分割线
initTable()的作用,是根据sizeCtl(负数的话,就是16)这个值申请长度为sizeCtl的Node<K,V>数组,放在这个map对象里并返回,作为本对象专用hash表
tabAt(a, b)的作用,是查找a表的第b个位置,如果找不到元素则返回null,否则返回第b个位置的hash表头
casTabAt(a, b, c, d)作用是,在a表的第b个hash表头上,创建并连接元素d,如果这个hash表头在此期间被其他线程操作过,返回false;没有其他线程操作,成功,返回true
spread(n):内部运算为(n ^ n >>> 16) & Integer.MAX_VALUE,先对n的低16位进行扰动处理,然后屏蔽符号位,结果为32位int型非负数
***********************分割线完了************/
helpTransfer代码:(协助扩容,这个函数的作用会讲到)

final Node<K,V>[] helpTransfer(Node<K,V>[] tab, Node<K,V> f) { Node<K,V>[] nextTab;int sc; //条件:原结点不为空,这个结点是协助结点(ForwardingNode)并且不处于最后 if (tab != null && (f instanceof ForwardingNode) && (nextTab = ((ForwardingNode<K,V>)f).nextTable) != null) { int rs = resizeStamp(tab.length); //sizeCtl是负数,标明还在扩容;tab和现在占用的table不同,说明扩容还在进行当中 while (nextTab == nextTable && table == tab && (sc = sizeCtl) < 0) { if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 || sc == rs + MAX_RESIZERS || transferIndex <= 0) break; //对现在的表长度做操作,如果没有改动,说明其他进程没有在扩容 if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) { transfer(tab, nextTab); break; } } return nextTab; } return table; } helpTransfer(Node<K,V>[] tab, Node<K,V> f)
这一层需要使用transfer扩容
/**********************其他函数作用的分割线
resizeStamp(n):返回结果是32768+Integer.numberOfLeadingZeros(n),是否扩容的标记
***********************分割线完了************/
上面还有一个函数addCount,内部操作也会扩容(感谢swenfang作者)

//check -1是删除,1是链表,2是红黑树 private final void addCount(long x, int check) { CounterCell[] as; long b, s; //利用CAS方法更新 baseCount 的值 if ((as = counterCells) != null || !U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {// 1 CounterCell a; long v; int m; boolean uncontended = true; if (as == null || (m = as.length - 1) < 0 || (a = as[ThreadLocalRandom.getProbe() & m]) == null || !(uncontended = U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) { // 多线程修改baseCount时,竞争失败的线程会执行fullAddCount(x, uncontended),把x的值插入到counterCell类中 fullAddCount(x, uncontended); // 2 return; } if (check <= 1)//这里添加之后,不是树就不用操作了 return; s = sumCount(); } //如果check值大于等于0 则需要检验是否需要进行扩容操作 if (check >= 0) { Node<K,V>[] tab, nt; int n, sc; // 当条件满足的时候开始扩容 while (s >= (long)(sc = sizeCtl) && (tab = table) != null && (n = tab.length) < MAXIMUM_CAPACITY) { int rs = resizeStamp(n); // 如果小于0 说明已经有线程在进行扩容了 if (sc < 0) { // 以下的情况说明已经有在扩容或者多线程进行了扩容,其他线程直接 break 不要进入扩容 if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 || sc == rs + MAX_RESIZERS || (nt = nextTable) == null || transferIndex <= 0) break; // 如果已经有其他线程在执行扩容操作 // 如果相等说明已经完成,可以继续扩容 if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) transfer(tab, nt); } // 当前线程是唯一的或是第一个发起扩容的线程 此时nextTable=null else if (U.compareAndSwapInt(this, SIZECTL, sc, (rs << RESIZE_STAMP_SHIFT) + 2)) transfer(tab, null); s = sumCount(); } } }
看上面的注释1,每次都会对 baseCount 加1,如果并发竞争太大,可能导致 U.compareAndSwapLong(this,BASECOUNT,b=baseCount,s = b + x) 失败,为了提高高并发的时候 baseCount 可见性的失败的问题,又避免一直重试,这样性能会有很大的影响,直接用fullAddCount函数完成整个过程
2.putAll操作(批量插入键值对)
putAll操作代码:

1 public void putAll(final Map<? extends K, ? extends V> map) { 2 this.tryPresize(map.size());//预先对表计算容量,防止重复扩容 3 for (final Map.Entry<? extends K, ? extends V> entry : map.entrySet()) { 4 this.putVal(entry.getKey(), (V)entry.getValue(), false); 5 } 6 }
putAll函数要合并一整个集合,在预先丈量容量的过程就会发生扩容,防止重复操作
tryPresize操作代码(感谢简书):

1 private final void tryPresize(int size) { 2 //计算扩容的目标size 3 // 给定的容量若>=MAXIMUM_CAPACITY的一半,直接扩容到允许的最大值,否则调用函数扩容 4 int c = (size >= (MAXIMUM_CAPACITY >>> 1)) ? MAXIMUM_CAPACITY : 5 tableSizeFor(size + (size >>> 1) + 1); 6 int sc; 7 while ((sc = sizeCtl) >= 0) { //没有正在初始化或扩容,或者说表还没有被初始化 8 Node<K,V>[] tab = table; int n; 9 //tab没有初始化 10 if(tab == null || (n = tab.length) == 0) { 11 n = (sc > c) ? sc : c; // 扩容阀值取较大者 12 //期间没有其他线程对表操作,则CAS将SIZECTL状态置为-1,表示正在进行初始化 13 //初始化之前,CAS设置sizeCtl=-1 14 if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) { 15 try { 16 if (table == tab) { 17 @SuppressWarnings("unchecked") 18 Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n]; 19 table = nt; 20 sc = n - (n >>> 2); //sc=0.75n,相当于扩容阈值 21 } 22 } finally { 23 // 此时并没有通过CAS赋值,因为其他想要执行初始化的线程, 24 // 发现sizeCtl=-1,就直接返回,从而确保任何情况, 25 // 只会有一个线程执行初始化操作。 26 sizeCtl = sc; 27 } 28 } 29 } 30 // 若欲扩容值不大于原阀值,或现有容量>=最值,什么都不用做了 31 //目标扩容size小于扩容阈值,或者容量超过最大限制时,不需要扩容 32 else if (c <= sc || n >= MAXIMUM_CAPACITY) 33 break; 34 //扩容 35 else if (tab == table) { 36 int rs = resizeStamp(n); 37 // sc<0表示,已经有其他线程正在扩容 38 if (sc < 0) { 39 Node<K,V>[] nt; 40 // RESIZE_STAMP_SHIFT=16,MAX_RESIZERS=2^15-1 41 // 1. (sc >>> RESIZE_STAMP_SHIFT) != rs :扩容线程数 > MAX_RESIZERS-1 42 // 2. sc == rs + 1 和 sc == rs + MAX_RESIZERS :表示什么??? 43 // 3. (nt = nextTable) == null :表示nextTable正在初始化 44 // transferIndex <= 0 :表示所有hash桶均分配出去 45 if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 || 46 sc == rs + MAX_RESIZERS || (nt = nextTable) == null || 47 transferIndex <= 0) 48 //如果不需要帮其扩容,直接返回 49 break; 50 //CAS设置sizeCtl=sizeCtl+1 51 if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) 52 //帮其扩容 53 transfer(tab, nt); 54 } 55 // 第一个执行扩容操作的线程,将sizeCtl设置为: 56 // (resizeStamp(n) << RESIZE_STAMP_SHIFT) + 2) 57 else if (U.compareAndSwapInt(this, SIZECTL, sc, (rs << RESIZE_STAMP_SHIFT) + 2)) 58 transfer(tab, null); 59 } 60 } 61 }
在数组没有初始化的情况下,tryPresize会开动初始化程序,sizeCtl(扩容阈值)设置-1,表示正在初始化。
不满足扩容条件(未达阈值,或者超过容量限制)不扩容,退出函数
满足扩容条件,如果所有的表都在扩容,或者线程过多,就退出函数;否则协助扩容。
并且扩容操作会多次执行,直到无需扩容为止
/**********************其他函数作用的分割线
tableSizeFor(n):n向上取整2的整数次方,若n是5,本身不是2的整数次方,但是向上有最近的8是2的整数次方,返回8;若n是4,本身就是2的整数次方,返回4
***********************分割线完了************/
putVal部分与第1个情况一致
3.remove操作(移除元素,底层实现是用null空值代替原位元素)
remove操作代码:

1 public boolean remove(final Object o, final Object o2) { 2 if (o == null) { 3 throw new NullPointerException(); 4 } 5 return o2 != null && this.replaceNode(o, null, o2) != null; 6 }
主要是replaceNode这里可能涉及协助扩容
replaceNode操作代码:

1 final V replaceNode(Object key, V value, Object cv) { 2 int hash = spread(key.hashCode()); 3 for (Node<K,V>[] tab = table;;) { 4 Node<K,V> f; int n, i, fh; 5 // table 还没初始化或key对应的 hash 桶为空 6 if (tab == null || (n = tab.length) == 0 || 7 (f = tabAt(tab, i = (n - 1) & hash)) == null) 8 break; 9 // 正在扩容 10 else if ((fh = f.hash) == MOVED) 11 tab = helpTransfer(tab, f); 12 else { 13 V oldVal = null; 14 boolean validated = false; 15 synchronized (f) { 16 // CAS 获取 tab[i] ,如果此时 tab[i] != f,说明其他线程修改了 tab[i] 17 // 回到 for 循环开始处,重新执行 18 if (tabAt(tab, i) == f) { 19 // node 链表 20 if (fh >= 0) { 21 validated = true; 22 for (Node<K,V> e = f, pred = null;;) { 23 K ek; 24 if (e.hash == hash && 25 ((ek = e.key) == key || 26 (ek != null && key.equals(ek)))) { 27 V ev = e.val; 28 // ev 代表参数期望值 29 // cv == null:直接更新value/删除节点 30 // cv 不为空,则只有在 key 的 oldVal 等于 31 // 期望值的时候,才更新 value/删除节点 32 if (cv == null || cv == ev || 33 (ev != null && cv.equals(ev))) { 34 oldVal = ev; 35 //更新value 36 if (value != null) 37 e.val = value; 38 //删除非头节点 39 else if (pred != null) 40 pred.next = e.next; 41 //删除头节点 42 else 43 // 因为已经获取了头结点锁,所以此时 44 // 不需要使用casTabAt 45 setTabAt(tab, i, e.next); 46 } 47 break; 48 } 49 //当前节点不是目标节点,继续遍历下一个节点 50 pred = e; 51 if ((e = e.next) == null) 52 //到达链表尾部,依旧没有找到,跳出循环 53 break; 54 } 55 } 56 //红黑树 57 else if (f instanceof TreeBin) { 58 validated = true; 59 TreeBin<K,V> t = (TreeBin<K,V>)f; 60 TreeNode<K,V> r, p; 61 if ((r = t.root) != null && 62 (p = r.findTreeNode(hash, key, null)) != null) { 63 V pv = p.val; 64 if (cv == null || cv == pv || 65 (pv != null && cv.equals(pv))) { 66 oldVal = pv; 67 if (value != null) 68 p.val = value; 69 else if (t.removeTreeNode(p)) 70 setTabAt(tab, i, untreeify(t.first)); 71 } 72 } 73 } 74 } 75 } 76 if (validated) { 77 if (oldVal != null) { 78 //如果删除了节点,更新size 79 if (value == null) 80 addCount(-1L, -1); 81 return oldVal; 82 } 83 break; 84 } 85 } 86 } 87 return null; 88 }
如果插入的表正好需要扩容,则开动transfer()协助扩容
在这之后,再对值进行替换,是空值的话,会删除旧值
如果发生了删除操作,在之后会执行addCount(-1L, -1);计数器减1
/**********************其他函数作用的分割线
setTabAt(tab, i, node):将tab表的第b个hash表头设成node
***********************分割线完了************/
4.replace操作(对已存在的键值对替换值)
两个参数的replace操作,返回替换出来的值:

public V replace(final K k, final V v) { if (k == null || v == null) { throw new NullPointerException(); } return this.replaceNode(k, v, null); }
三个参数的replace操作,第三个参数是对照值,返回值是布尔值,取决于键对应的键值对是否存在,以及是否替换成功(如果原键值对的值和对照值不同则不替换)

public boolean replace(final K k, final V v, final V v2) { if (k == null || v == null || v2 == null) { throw new NullPointerException(); } return this.replaceNode(k, v2, v) != null; }
这俩函数下一层也是replaceNode
5.computeIfAbsent操作(若key对应的value为空,会将第二个参数的返回值存入并返回)

1 public V computeIfAbsent(final K k, final Function<? super K, ? extends V> function) { 2 if (k == null || function == null) { 3 throw new NullPointerException(); 4 } 5 final int spread = spread(k.hashCode()); 6 Object o = null; 7 int n = 0; 8 Object[] array = this.table; 9 while (true) { 10 final int length; 11 //数组没有初始化,初始化一个先 12 if (array == null || (length = ((Node<K, V>[])array).length) == 0) { 13 array = this.initTable(); 14 } 15 else { 16 final int n2; 17 final Map.Entry<K, V> tab; 18 //查的对应hash表没有数据,直接function.apply(k)创建一份值 19 if ((tab = (Map.Entry<K, V>)tabAt((Node<K, V>[])array, n2 = (length - 1 & spread))) == null) { 20 final ReservationNode<Object, Object> reservationNode = new ReservationNode<Object, Object>(); 21 synchronized (reservationNode) { 22 if (casTabAt((Node<K, V>[])array, n2, null, (Node<K, V>)reservationNode)) { 23 n = 1; 24 Node<Object, Object> node = null; 25 try { 26 if ((o = function.apply(k)) != null) { 27 node = new Node<Object, Object>(spread, k, o, null); 28 } 29 } 30 finally { 31 setTabAt((Node<K, V>[])array, n2, (Node<K, V>)node); 32 } 33 } 34 } 35 if (n != 0) { 36 break; 37 } 38 continue; 39 } 40 //查的对应hash表有数据 41 else { 42 final int hash; 43 //对应hash表-1,协助扩容 44 if ((hash = ((Node)tab).hash) == -1) { 45 array = this.helpTransfer((Node<K, V>[])array, (Node<K, V>)tab); 46 } 47 else { 48 boolean b = false; 49 synchronized (tab) { 50 Label_0433: { 51 //表没有发生变化就可以操作,否则就是被占用了 52 if (tabAt((Node<K, V>[])array, n2) == tab) { 53 //链表 54 if (hash >= 0) { 55 n = 1; 56 Node<K, V> next = (Node<K, V>)tab; 57 K key; 58 while (next.hash != spread || ((key = next.key) != k && (key == null || !k.equals(key)))) { 59 final Node<K, V> node2 = next; 60 if ((next = next.next) == null) { 61 if ((o = function.apply(k)) != null) { 62 b = true; 63 node2.next = (Node<K, V>)new Node<Object, Object>(spread, (K)k, (V)o, null); 64 } 65 break Label_0433; 66 } 67 ++n; 68 } 69 o = next.val; 70 } 71 //红黑树 72 else if (tab instanceof TreeBin) { 73 n = 2; 74 final TreeBin treeBin = (TreeBin)tab; 75 final Object root; 76 final TreeNode<K, Object> treeNode; 77 if ((root = treeBin.root) != null && (treeNode = ((TreeNode<K, Object>)root).findTreeNode(spread, k, null)) != null) { 78 o = treeNode.val; 79 } 80 else if ((o = function.apply(k)) != null) { 81 b = true; 82 treeBin.putTreeVal(spread, k, o); 83 } 84 } 85 } 86 } 87 } 88 if (n == 0) { 89 continue; 90 } 91 if (n >= 8) { 92 this.treeifyBin((Node<K, V>[])array, n2); 93 } 94 //上述b为是否找不到元素 95 if (!b) { 96 return (V)o; 97 } 98 break; 99 } 100 } 101 } 102 } 103 //上一个操作中,hash值能对应表,并且找到值之后就返回了 104 //这里只有找不到值,需要重赋值才执行到 105 if (o != null) { 106 this.addCount(1L, n); 107 } 108 return (V)o; 109 }
这里网上找不到代码,用rt.jar反编译的代码代替
如果找到的表预备扩容,会先执行helpTransfer
在此之后,如果是添加元素而非单纯的查找元素,就会执行addCount操作,这俩操作都会扩容
扩容的内部逻辑
读了一下transfer代码,了解了很多东西。
首先是为什么初始容量必须为2的整数次方,并且扩容是2倍2倍地扩:使扩容前的单个节点无需与其他节点交互,只控制自己的节点和跳过去的新节点。
例如hash表有8个,要扩容为16个,那么之前放在7表的数据,可能对16取模会余15,也可能还是7;其他7种元素只能分配到7和15以外的0-15余数;这样的话,只需把部分数据往15表上移,保留一部分到原表中。
并且数程序会仔细判定数据的插入顺序,这使得原来数据的插入顺序扩容之后不会打乱。
再者还有为什么有ForwardingNode和helpTransfer():单个扩容线程的扩容速度会很慢,可能有线程需要插入尾端数据,但是那个扩容线程在头端。这样的话,给对应hash表设置灵活的扩容机制比较重要。读线程不需修改数据,直接把数据读了就是;写线程要考虑新插入的数据位置,让这个线程协助扩容,再完成插入操作的话这个线程的写操作不会堵塞很久。
这就是ForwardingNode节点和helpTransfer()的作用。

1 private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) { 2 int n = tab.length, stride; 3 //计算每次迁移的node个数(MIN_TRANSFER_STRIDE该值作为下限,以避免扩容线程过多) 4 if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE) 5 // 确保每次迁移的node个数不少于16个 6 stride = MIN_TRANSFER_STRIDE; 7 // nextTab为扩容中的临时table 8 if (nextTab == null) { 9 try { 10 //扩容一倍 11 @SuppressWarnings("unchecked") 12 // 1. 新建一个 node 数组,容量为之前的两倍 13 Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1]; 14 nextTab = nt; 15 } catch (Throwable ex) { // try to copy with OOME 16 sizeCtl = Integer.MAX_VALUE; 17 return; 18 } 19 nextTable = nextTab; 20 // transferIndex为扩容复制过程中的桶首节点遍历索引 21 // 所以从n开始,表示从后向前遍历 22 transferIndex = n; 23 } 24 int nextn = nextTab.length; 25 // ForwardingNode是Node节点的直接子类,是扩容过程中的特殊桶首节点 26 // 该类中没有key,value,next 27 // hash值为特定的-1 28 // 附加Node<K,V>[] nextTable变量指向扩容中的nextTab 29 // 在find方法中,将扩容中的查询操作导入到nextTab上 30 //2. 新建forwardingNode引用,在之后会用到 31 ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab); 32 boolean advance = true; 33 // 循环的关键变量,判断是否已经扩容完成,完成就 return , 退出循环 34 boolean finishing = false; 35 //【1】逆序迁移已经获取到的hash桶集合,如果迁移完毕,则更新transferIndex, 36 // 获取下一批待迁移的hash桶 37 //【2】如果transferIndex=0,表示所以hash桶均被分配,将i置为-1, 38 // 准备退出transfer方法 39 for (int i = 0, bound = 0;;) { 40 Node<K,V> f; int fh; 41 // 3. 确定遍历中的索引i(更新待迁移的hash桶索引) 42 // 循环的关键 i , i-- 操作保证了倒叙遍历数组 43 while (advance) { 44 int nextIndex, nextBound; 45 // 更新迁移索引i 46 if (--i >= bound || finishing) 47 advance = false; 48 // transferIndex = 0表示table中所有数组元素都已经有其他线程负责扩容 49 // nextIndex=transferIndex=n=tab.length(默认16) 50 else if ((nextIndex = transferIndex) <= 0) { 51 // transferIndex<=0表示已经没有需要迁移的hash桶, 52 // 将i置为-1,线程准备退出 53 i = -1; 54 advance = false; 55 } 56 //cas无锁算法设置 transferIndex = transferIndex - stride 57 // 尝试更新transferIndex,获取当前线程执行扩容复制的索引区间 58 // 更新成功,则当前线程负责完成索引为(nextBound,nextIndex)之间的桶首节点扩容 59 //当迁移完bound这个桶后,尝试更新transferIndex,获取下一批待迁移的hash桶 60 else if (U.compareAndSwapInt 61 (this, TRANSFERINDEX, nextIndex, 62 nextBound = (nextIndex > stride ? 63 nextIndex - stride : 0))) { 64 bound = nextBound; 65 i = nextIndex - 1; 66 advance = false; 67 } 68 } //退出transfer 69 //4.将原数组中的元素复制到新数组中去 70 //4.5 for循环退出,扩容结束修改sizeCtl属性 71 // i<0 说明已经遍历完旧的数组tab;i>=n什么时候有可能呢?在下面看到i=n,所以目前i最大应该是n吧 72 // i+n>=nextn,nextn=nextTab.length,所以如果满足i+n>=nextn说明已经扩容完成 73 if (i < 0 || i >= n || i + n >= nextn) { 74 int sc; 75 if (finishing) { // a 76 //最后一个迁移的线程,recheck后,做收尾工作,然后退出 77 nextTable = null; 78 table = nextTab; 79 // 扩容成功,设置新sizeCtl,仍然为总大小的0.75 80 sizeCtl = (n << 1) - (n >>> 1); 81 return; 82 } 83 84 // 第一个扩容的线程,执行transfer方法之前,会设置 sizeCtl = 85 // (resizeStamp(n) << RESIZE_STAMP_SHIFT) + 2) 86 // 后续帮其扩容的线程,执行transfer方法之前,会设置 sizeCtl = sizeCtl+1 87 // 每一个退出transfer的方法的线程,退出之前,会设置 sizeCtl = sizeCtl-1 88 // 那么最后一个线程退出时: 89 // 必然有sc == (resizeStamp(n) << RESIZE_STAMP_SHIFT) + 2), 90 // 即 (sc - 2) == resizeStamp(n) << RESIZE_STAMP_SHIFT 91 92 if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) { 93 // 如果有多个线程进行扩容,那么这个值在第二个线程以后就不会相等,因为 94 // sizeCtl 已经被减1了,所以后面的线程只能直接返回, 95 // 始终保证只有一个线程执行了a(上面的注释a) 96 if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT) 97 return; 98 // finishing 和 advance 保证线程已经扩容完成了可以退出循环 99 finishing = advance = true; 100 //最后退出的线程要重新check下是否全部迁移完毕 101 i = n; 102 } 103 } 104 // 当前table节点为空,不需要复制,直接放入ForwardingNode 105 //4.1 当前数组中第i个元素为null,用CAS设置成特殊节点forwardingNode(可以理解成占位符) 106 //如果 tab[i] 为 null,那么就把 fwd 插入到 tab[i],表明这个节点已经处理过了 107 else if ((f = tabAt(tab, i)) == null) 108 advance = casTabAt(tab, i, null, fwd); 109 // 当前table节点已经是ForwardingNode 110 // 表示已经被其他线程处理了,则直接往前遍历 111 // 通过CAS读写ForwardingNode节点状态,达到多线程互斥处理 112 // 4.2 如果遍历到ForwardingNode节点说明这个点已经被处理过了直接跳过 113 // 这里是控制并发扩容的核心 114 // 如果 f.hash=-1 的话说明该节点为 ForwardingNode,说明该节点已经处理过了 115 else if ((fh = f.hash) == MOVED) 116 advance = true; 117 //迁移node节点 118 else { 119 // 锁住当前桶首节点 120 synchronized (f) { 121 if (tabAt(tab, i) == f) { 122 Node<K,V> ln, hn; 123 // 链表节点复制(链表迁移) 124 if (fh >= 0) { 125 // 4.3 处理当前节点为链表的头结点的情况,构造两个链表,一个是原链表 126 // 另一个是原链表的反序排列 127 int runBit = fh & n; 128 Node<K,V> lastRun = f; 129 //将node链表,分成2个新的node链表 130 // 这边还对链表进行遍历,这边的算法和hashMap的算法又不一样了,对半拆分 131 // 把链表拆分为,hash&n 等于0和不等于0的,然后分别放在新表的i和i+n位置 132 // 此方法同 HashMap 的 resize 133 for (Node<K,V> p = f.next; p != null; p = p.next) { 134 int b = p.hash & n; 135 if (b != runBit) { 136 runBit = b; 137 lastRun = p; 138 } 139 } 140 if (runBit == 0) { 141 ln = lastRun; 142 hn = null; 143 } 144 else { 145 hn = lastRun; 146 ln = null; 147 } 148 for (Node<K,V> p = f; p != lastRun; p = p.next) { 149 int ph = p.hash; K pk = p.key; V pv = p.val; 150 if ((ph & n) == 0) 151 ln = new Node<K,V>(ph, pk, pv, ln); 152 else 153 hn = new Node<K,V>(ph, pk, pv, hn); 154 } 155 //将新node链表赋给nextTab 156 //在nextTable的i位置上插入一个链表 157 setTabAt(nextTab, i, ln); 158 //在nextTable的i+n的位置上插入另一个链表 159 setTabAt(nextTab, i + n, hn); 160 // 扩容成功后,设置ForwardingNode节点 161 //在table的i位置上插入forwardNode节点表示已经处理过该节点 162 // 把已经替换的节点的旧tab的i的位置用fwd替换,fwd包含nextTab 163 setTabAt(tab, i, fwd); 164 //设置advance为true 返回到上面的while循环中 就可以执行i--操作 165 advance = true; 166 } 167 // 红黑树节点复制(红黑树迁移) 168 //4.4 处理当前节点是TreeBin时的情况,操作和上面的类似 169 else if (f instanceof TreeBin) { 170 TreeBin<K,V> t = (TreeBin<K,V>)f; 171 TreeNode<K,V> lo = null, loTail = null; 172 TreeNode<K,V> hi = null, hiTail = null; 173 int lc = 0, hc = 0; 174 for (Node<K,V> e = t.first; e != null; e = e.next) { 175 int h = e.hash; 176 TreeNode<K,V> p = new TreeNode<K,V> 177 (h, e.key, e.val, null, null); 178 //可看出本节点扩容之后应该放低位节点 179 if ((h & n) == 0) { 180 if ((p.prev = loTail) == null) 181 lo = p; 182 else 183 loTail.next = p; 184 loTail = p; 185 ++lc; 186 } 187 //扩容之后应该放高位节点 188 else { 189 if ((p.prev = hiTail) == null) 190 hi = p; 191 else 192 hiTail.next = p; 193 hiTail = p; 194 ++hc; 195 } 196 } 197 // 判断扩容后是否还需要红黑树 198 ln = (lc <= UNTREEIFY_THRESHOLD) ? untreeify(lo) : 199 (hc != 0) ? new TreeBin<K,V>(lo) : t; 200 hn = (hc <= UNTREEIFY_THRESHOLD) ? untreeify(hi) : 201 (lc != 0) ? new TreeBin<K,V>(hi) : t; 202 setTabAt(nextTab, i, ln); 203 setTabAt(nextTab, i + n, hn); 204 // 扩容成功后,设置ForwardingNode节点 205 setTabAt(tab, i, fwd); 206 advance = true; 207 } 208 } 209 } 210 } 211 } 212 }