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)

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM