zoukankan      html  css  js  c++  java
  • 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)
  • 相关阅读:
    爬虫前面
    常用模块学习
    函数、递归、内置函数
    迭代器、装饰器、软件开发规范
    python基础
    列表、字典、集合
    介绍、基本语法、流程控制
    python学习的第一个星期
    vmware使用nat连接配置
    Vue API 3模板语法 ,指令
  • 原文地址:https://www.cnblogs.com/dgutfly/p/11425599.html
Copyright © 2011-2022 走看看