JDK1.8 论ConcurrentHashMap是如何扩容的

导致扩容的情况

  在了解JDK1.8的ConcurrentHashMap扩容机制之前,要先知道ConcurrentHashMap什么情况会导致扩容。

  1.put操作(插入键值对)

  put函数的操作要通过putVal操作,如果有特殊情况要扩容。

  put操作代码:

1 public V put(K key, V value) {
2     return putVal(key, value, false);
3 }
public V put(K key, V value)

  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 }
final V putVal(K key, V value, boolean onlyIfAbsent)

  插入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)
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();
        }
    }
}
void addCount(long x, int check)

  看上面的注释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(final Map<? extends K, ? extends V> map)

  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     }
void tryPresize(int size)

  在数组没有初始化的情况下,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 }
boolean remove(final Object o, final Object o2)

  主要是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 }
V replaceNode(Object key, V value, Object cv)

  如果插入的表正好需要扩容,则开动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);
    }
V replace(final K k, final V v)

  三个参数的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;
    }
boolean replace(final K k, final V v, final V v2)

  这俩函数下一层也是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     }
V computeIfAbsent(final K k, final Function<? super K, ? extends V> function)

  这里网上找不到代码,用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     }
private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab)

 

posted @ 2019-09-10 18:23  DGUT_FLY  阅读(3527)  评论(0编辑  收藏  举报