導致擴容的情況
在了解JDK1.8的ConcurrentHashMap擴容機制之前,要先知道ConcurrentHashMap什么情況會導致擴容。
1.put操作(插入鍵值對)
put函數的操作要通過putVal操作,如果有特殊情況要擴容。
put操作代碼:
1 public V put(K key, V value) { 2 return putVal(key, value, false); 3 }
putVal代碼(注釋感謝簡書作者代碼potty):
1 //onlyIfAbsent跟HashMap一樣,就是判斷是否要覆蓋,默認為false,覆蓋 2 final V putVal(K key, V value, boolean onlyIfAbsent) { 3 if (key == null || value == null) throw new NullPointerException(); 4 int hash = spread(key.hashCode()); 5 //binCount=0說明首節點插入,未進行鏈表或紅黑樹操作,因為后面會對這個值進行更改 6 int binCount = 0; 7 for (Node<K,V>[] tab = table;;) { 8 Node<K,V> f; int n, i, fh; 9 //如果數組為空或者長度為0,進行初始化工作 10 if (tab == null || (n = tab.length) == 0) 11 tab = initTable(); 12 //如果獲取位置的節點為空,說明是首節點插入情況 13 else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) { 14 if (casTabAt(tab, i, null,new Node<K,V>(hash, key, value, null))) 15 break;//直接在首節點插入對應元素,不用加鎖 16 } 17 //如果hash值等於MOVEN(默認-1),說明是協助擴容,與transfer里面的ForwardingNode類有關 18 else if ((fh = f.hash) == MOVED) 19 tab = helpTransfer(tab, f);//協助擴容,這里就是用到transfer函數的地方 20 else { 21 V oldVal = null; 22 //對桶的首節點進行加鎖 23 synchronized (f) { 24 //雙重判定,為了防止在當前線程進來之前,i地址所對應對象已經更改 25 if (tabAt(tab, i) == f) { 26 //TreeBin類型的hash值默認設置為了-2 27 if (fh >= 0) { 28 binCount = 1; 29 for (Node<K,V> e = f;; ++binCount) { 30 K ek; 31 //在當前桶中找到位置跳出 32 if (e.hash == hash && 33 ((ek = e.key) == key || 34 (ek != null && key.equals(ek)))) { 35 oldVal = e.val; 36 if (!onlyIfAbsent) 37 e.val = value; 38 break; 39 } 40 Node<K,V> pred = e; 41 //當到桶的結尾還沒找到,則新增一個Node 42 if ((e = e.next) == null) { 43 pred.next = new Node<K,V>(hash, key, 44 value, null); 45 break; 46 } 47 } 48 } 49 //如果hash小於0,判斷是否是TreeBin的實例 50 else if (f instanceof TreeBin) { 51 Node<K,V> p; 52 binCount = 2; 53 if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key, 54 value)) != null) { 55 oldVal = p.val; 56 if (!onlyIfAbsent) 57 p.val = value; 58 } 59 } 60 } 61 } 62 //如果binCount值不等於0,說明進行了鏈表或者紅黑樹操作 63 if (binCount != 0) { 64 //如果binCount大於8則進行樹化,但真正的轉換成紅黑樹不是8的長度 65 //當長度超過64才會真正的樹化,處於8-64之間的還只是數組擴容 66 if (binCount >= TREEIFY_THRESHOLD) 67 //這個就是樹化鏈表操作 68 treeifyBin(tab, i); 69 if (oldVal != null) 70 return oldVal; 71 break; 72 } 73 } 74 } 75 //計數方法 76 addCount(1L, binCount); 77 return null; 78 }
插入1對鍵值對時,先判斷數組是否空,長度是否為0(沒有就先創建1個數組),再判斷數組對應表是否為空節點(沒有表直接現創一個,完成后退出函數)
這個節點的hash是-1的情況(MOVEN)下,需要先進行協助擴容,再進行下一步操作
之后,在這個表上執行插入操作,如果插入之后,這個表的長度超過8,會進行下一步處理:
表數據量在8-64之間時,會優先擴容hash數組;只有表數據量超過64時,內部才會執行樹化。
(防止hash表的查詢時間復雜度從O(1)過快退化成O(lg n),如果不重寫類比較器Compare,查詢復雜度會更進一步退化為O(n))
/**********************其他函數作用的分割線
initTable()的作用,是根據sizeCtl(負數的話,就是16)這個值申請長度為sizeCtl的Node<K,V>數組,放在這個map對象里並返回,作為本對象專用hash表
tabAt(a, b)的作用,是查找a表的第b個位置,如果找不到元素則返回null,否則返回第b個位置的hash表頭
casTabAt(a, b, c, d)作用是,在a表的第b個hash表頭上,創建並連接元素d,如果這個hash表頭在此期間被其他線程操作過,返回false;沒有其他線程操作,成功,返回true
spread(n):內部運算為(n ^ n >>> 16) & Integer.MAX_VALUE,先對n的低16位進行擾動處理,然后屏蔽符號位,結果為32位int型非負數
***********************分割線完了************/
helpTransfer代碼:(協助擴容,這個函數的作用會講到)
final Node<K,V>[] helpTransfer(Node<K,V>[] tab, Node<K,V> f) { Node<K,V>[] nextTab;int sc; //條件:原結點不為空,這個結點是協助結點(ForwardingNode)並且不處於最后 if (tab != null && (f instanceof ForwardingNode) && (nextTab = ((ForwardingNode<K,V>)f).nextTable) != null) { int rs = resizeStamp(tab.length); //sizeCtl是負數,標明還在擴容;tab和現在占用的table不同,說明擴容還在進行當中 while (nextTab == nextTable && table == tab && (sc = sizeCtl) < 0) { if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 || sc == rs + MAX_RESIZERS || transferIndex <= 0) break; //對現在的表長度做操作,如果沒有改動,說明其他進程沒有在擴容 if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) { transfer(tab, nextTab); break; } } return nextTab; } return table; } helpTransfer(Node<K,V>[] tab, Node<K,V> f)
這一層需要使用transfer擴容
/**********************其他函數作用的分割線
resizeStamp(n):返回結果是32768+Integer.numberOfLeadingZeros(n),是否擴容的標記
***********************分割線完了************/
上面還有一個函數addCount,內部操作也會擴容(感謝swenfang作者)
//check -1是刪除,1是鏈表,2是紅黑樹 private final void addCount(long x, int check) { CounterCell[] as; long b, s; //利用CAS方法更新 baseCount 的值 if ((as = counterCells) != null || !U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {// 1 CounterCell a; long v; int m; boolean uncontended = true; if (as == null || (m = as.length - 1) < 0 || (a = as[ThreadLocalRandom.getProbe() & m]) == null || !(uncontended = U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) { // 多線程修改baseCount時,競爭失敗的線程會執行fullAddCount(x, uncontended),把x的值插入到counterCell類中 fullAddCount(x, uncontended); // 2 return; } if (check <= 1)//這里添加之后,不是樹就不用操作了 return; s = sumCount(); } //如果check值大於等於0 則需要檢驗是否需要進行擴容操作 if (check >= 0) { Node<K,V>[] tab, nt; int n, sc; // 當條件滿足的時候開始擴容 while (s >= (long)(sc = sizeCtl) && (tab = table) != null && (n = tab.length) < MAXIMUM_CAPACITY) { int rs = resizeStamp(n); // 如果小於0 說明已經有線程在進行擴容了 if (sc < 0) { // 以下的情況說明已經有在擴容或者多線程進行了擴容,其他線程直接 break 不要進入擴容 if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 || sc == rs + MAX_RESIZERS || (nt = nextTable) == null || transferIndex <= 0) break; // 如果已經有其他線程在執行擴容操作 // 如果相等說明已經完成,可以繼續擴容 if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) transfer(tab, nt); } // 當前線程是唯一的或是第一個發起擴容的線程 此時nextTable=null else if (U.compareAndSwapInt(this, SIZECTL, sc, (rs << RESIZE_STAMP_SHIFT) + 2)) transfer(tab, null); s = sumCount(); } } }
看上面的注釋1,每次都會對 baseCount 加1,如果並發競爭太大,可能導致 U.compareAndSwapLong(this,BASECOUNT,b=baseCount,s = b + x) 失敗,為了提高高並發的時候 baseCount 可見性的失敗的問題,又避免一直重試,這樣性能會有很大的影響,直接用fullAddCount函數完成整個過程
2.putAll操作(批量插入鍵值對)
putAll操作代碼:
1 public void putAll(final Map<? extends K, ? extends V> map) { 2 this.tryPresize(map.size());//預先對表計算容量,防止重復擴容 3 for (final Map.Entry<? extends K, ? extends V> entry : map.entrySet()) { 4 this.putVal(entry.getKey(), (V)entry.getValue(), false); 5 } 6 }
putAll函數要合並一整個集合,在預先丈量容量的過程就會發生擴容,防止重復操作
tryPresize操作代碼(感謝簡書):
1 private final void tryPresize(int size) { 2 //計算擴容的目標size 3 // 給定的容量若>=MAXIMUM_CAPACITY的一半,直接擴容到允許的最大值,否則調用函數擴容 4 int c = (size >= (MAXIMUM_CAPACITY >>> 1)) ? MAXIMUM_CAPACITY : 5 tableSizeFor(size + (size >>> 1) + 1); 6 int sc; 7 while ((sc = sizeCtl) >= 0) { //沒有正在初始化或擴容,或者說表還沒有被初始化 8 Node<K,V>[] tab = table; int n; 9 //tab沒有初始化 10 if(tab == null || (n = tab.length) == 0) { 11 n = (sc > c) ? sc : c; // 擴容閥值取較大者 12 //期間沒有其他線程對表操作,則CAS將SIZECTL狀態置為-1,表示正在進行初始化 13 //初始化之前,CAS設置sizeCtl=-1 14 if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) { 15 try { 16 if (table == tab) { 17 @SuppressWarnings("unchecked") 18 Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n]; 19 table = nt; 20 sc = n - (n >>> 2); //sc=0.75n,相當於擴容閾值 21 } 22 } finally { 23 // 此時並沒有通過CAS賦值,因為其他想要執行初始化的線程, 24 // 發現sizeCtl=-1,就直接返回,從而確保任何情況, 25 // 只會有一個線程執行初始化操作。 26 sizeCtl = sc; 27 } 28 } 29 } 30 // 若欲擴容值不大於原閥值,或現有容量>=最值,什么都不用做了 31 //目標擴容size小於擴容閾值,或者容量超過最大限制時,不需要擴容 32 else if (c <= sc || n >= MAXIMUM_CAPACITY) 33 break; 34 //擴容 35 else if (tab == table) { 36 int rs = resizeStamp(n); 37 // sc<0表示,已經有其他線程正在擴容 38 if (sc < 0) { 39 Node<K,V>[] nt; 40 // RESIZE_STAMP_SHIFT=16,MAX_RESIZERS=2^15-1 41 // 1. (sc >>> RESIZE_STAMP_SHIFT) != rs :擴容線程數 > MAX_RESIZERS-1 42 // 2. sc == rs + 1 和 sc == rs + MAX_RESIZERS :表示什么??? 43 // 3. (nt = nextTable) == null :表示nextTable正在初始化 44 // transferIndex <= 0 :表示所有hash桶均分配出去 45 if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 || 46 sc == rs + MAX_RESIZERS || (nt = nextTable) == null || 47 transferIndex <= 0) 48 //如果不需要幫其擴容,直接返回 49 break; 50 //CAS設置sizeCtl=sizeCtl+1 51 if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) 52 //幫其擴容 53 transfer(tab, nt); 54 } 55 // 第一個執行擴容操作的線程,將sizeCtl設置為: 56 // (resizeStamp(n) << RESIZE_STAMP_SHIFT) + 2) 57 else if (U.compareAndSwapInt(this, SIZECTL, sc, (rs << RESIZE_STAMP_SHIFT) + 2)) 58 transfer(tab, null); 59 } 60 } 61 }
在數組沒有初始化的情況下,tryPresize會開動初始化程序,sizeCtl(擴容閾值)設置-1,表示正在初始化。
不滿足擴容條件(未達閾值,或者超過容量限制)不擴容,退出函數
滿足擴容條件,如果所有的表都在擴容,或者線程過多,就退出函數;否則協助擴容。
並且擴容操作會多次執行,直到無需擴容為止
/**********************其他函數作用的分割線
tableSizeFor(n):n向上取整2的整數次方,若n是5,本身不是2的整數次方,但是向上有最近的8是2的整數次方,返回8;若n是4,本身就是2的整數次方,返回4
***********************分割線完了************/
putVal部分與第1個情況一致
3.remove操作(移除元素,底層實現是用null空值代替原位元素)
remove操作代碼:
1 public boolean remove(final Object o, final Object o2) { 2 if (o == null) { 3 throw new NullPointerException(); 4 } 5 return o2 != null && this.replaceNode(o, null, o2) != null; 6 }
主要是replaceNode這里可能涉及協助擴容
replaceNode操作代碼:
1 final V replaceNode(Object key, V value, Object cv) { 2 int hash = spread(key.hashCode()); 3 for (Node<K,V>[] tab = table;;) { 4 Node<K,V> f; int n, i, fh; 5 // table 還沒初始化或key對應的 hash 桶為空 6 if (tab == null || (n = tab.length) == 0 || 7 (f = tabAt(tab, i = (n - 1) & hash)) == null) 8 break; 9 // 正在擴容 10 else if ((fh = f.hash) == MOVED) 11 tab = helpTransfer(tab, f); 12 else { 13 V oldVal = null; 14 boolean validated = false; 15 synchronized (f) { 16 // CAS 獲取 tab[i] ,如果此時 tab[i] != f,說明其他線程修改了 tab[i] 17 // 回到 for 循環開始處,重新執行 18 if (tabAt(tab, i) == f) { 19 // node 鏈表 20 if (fh >= 0) { 21 validated = true; 22 for (Node<K,V> e = f, pred = null;;) { 23 K ek; 24 if (e.hash == hash && 25 ((ek = e.key) == key || 26 (ek != null && key.equals(ek)))) { 27 V ev = e.val; 28 // ev 代表參數期望值 29 // cv == null:直接更新value/刪除節點 30 // cv 不為空,則只有在 key 的 oldVal 等於 31 // 期望值的時候,才更新 value/刪除節點 32 if (cv == null || cv == ev || 33 (ev != null && cv.equals(ev))) { 34 oldVal = ev; 35 //更新value 36 if (value != null) 37 e.val = value; 38 //刪除非頭節點 39 else if (pred != null) 40 pred.next = e.next; 41 //刪除頭節點 42 else 43 // 因為已經獲取了頭結點鎖,所以此時 44 // 不需要使用casTabAt 45 setTabAt(tab, i, e.next); 46 } 47 break; 48 } 49 //當前節點不是目標節點,繼續遍歷下一個節點 50 pred = e; 51 if ((e = e.next) == null) 52 //到達鏈表尾部,依舊沒有找到,跳出循環 53 break; 54 } 55 } 56 //紅黑樹 57 else if (f instanceof TreeBin) { 58 validated = true; 59 TreeBin<K,V> t = (TreeBin<K,V>)f; 60 TreeNode<K,V> r, p; 61 if ((r = t.root) != null && 62 (p = r.findTreeNode(hash, key, null)) != null) { 63 V pv = p.val; 64 if (cv == null || cv == pv || 65 (pv != null && cv.equals(pv))) { 66 oldVal = pv; 67 if (value != null) 68 p.val = value; 69 else if (t.removeTreeNode(p)) 70 setTabAt(tab, i, untreeify(t.first)); 71 } 72 } 73 } 74 } 75 } 76 if (validated) { 77 if (oldVal != null) { 78 //如果刪除了節點,更新size 79 if (value == null) 80 addCount(-1L, -1); 81 return oldVal; 82 } 83 break; 84 } 85 } 86 } 87 return null; 88 }
如果插入的表正好需要擴容,則開動transfer()協助擴容
在這之后,再對值進行替換,是空值的話,會刪除舊值
如果發生了刪除操作,在之后會執行addCount(-1L, -1);計數器減1
/**********************其他函數作用的分割線
setTabAt(tab, i, node):將tab表的第b個hash表頭設成node
***********************分割線完了************/
4.replace操作(對已存在的鍵值對替換值)
兩個參數的replace操作,返回替換出來的值:
public V replace(final K k, final V v) { if (k == null || v == null) { throw new NullPointerException(); } return this.replaceNode(k, v, null); }
三個參數的replace操作,第三個參數是對照值,返回值是布爾值,取決於鍵對應的鍵值對是否存在,以及是否替換成功(如果原鍵值對的值和對照值不同則不替換)
public boolean replace(final K k, final V v, final V v2) { if (k == null || v == null || v2 == null) { throw new NullPointerException(); } return this.replaceNode(k, v2, v) != null; }
這倆函數下一層也是replaceNode
5.computeIfAbsent操作(若key對應的value為空,會將第二個參數的返回值存入並返回)
1 public V computeIfAbsent(final K k, final Function<? super K, ? extends V> function) { 2 if (k == null || function == null) { 3 throw new NullPointerException(); 4 } 5 final int spread = spread(k.hashCode()); 6 Object o = null; 7 int n = 0; 8 Object[] array = this.table; 9 while (true) { 10 final int length; 11 //數組沒有初始化,初始化一個先 12 if (array == null || (length = ((Node<K, V>[])array).length) == 0) { 13 array = this.initTable(); 14 } 15 else { 16 final int n2; 17 final Map.Entry<K, V> tab; 18 //查的對應hash表沒有數據,直接function.apply(k)創建一份值 19 if ((tab = (Map.Entry<K, V>)tabAt((Node<K, V>[])array, n2 = (length - 1 & spread))) == null) { 20 final ReservationNode<Object, Object> reservationNode = new ReservationNode<Object, Object>(); 21 synchronized (reservationNode) { 22 if (casTabAt((Node<K, V>[])array, n2, null, (Node<K, V>)reservationNode)) { 23 n = 1; 24 Node<Object, Object> node = null; 25 try { 26 if ((o = function.apply(k)) != null) { 27 node = new Node<Object, Object>(spread, k, o, null); 28 } 29 } 30 finally { 31 setTabAt((Node<K, V>[])array, n2, (Node<K, V>)node); 32 } 33 } 34 } 35 if (n != 0) { 36 break; 37 } 38 continue; 39 } 40 //查的對應hash表有數據 41 else { 42 final int hash; 43 //對應hash表-1,協助擴容 44 if ((hash = ((Node)tab).hash) == -1) { 45 array = this.helpTransfer((Node<K, V>[])array, (Node<K, V>)tab); 46 } 47 else { 48 boolean b = false; 49 synchronized (tab) { 50 Label_0433: { 51 //表沒有發生變化就可以操作,否則就是被占用了 52 if (tabAt((Node<K, V>[])array, n2) == tab) { 53 //鏈表 54 if (hash >= 0) { 55 n = 1; 56 Node<K, V> next = (Node<K, V>)tab; 57 K key; 58 while (next.hash != spread || ((key = next.key) != k && (key == null || !k.equals(key)))) { 59 final Node<K, V> node2 = next; 60 if ((next = next.next) == null) { 61 if ((o = function.apply(k)) != null) { 62 b = true; 63 node2.next = (Node<K, V>)new Node<Object, Object>(spread, (K)k, (V)o, null); 64 } 65 break Label_0433; 66 } 67 ++n; 68 } 69 o = next.val; 70 } 71 //紅黑樹 72 else if (tab instanceof TreeBin) { 73 n = 2; 74 final TreeBin treeBin = (TreeBin)tab; 75 final Object root; 76 final TreeNode<K, Object> treeNode; 77 if ((root = treeBin.root) != null && (treeNode = ((TreeNode<K, Object>)root).findTreeNode(spread, k, null)) != null) { 78 o = treeNode.val; 79 } 80 else if ((o = function.apply(k)) != null) { 81 b = true; 82 treeBin.putTreeVal(spread, k, o); 83 } 84 } 85 } 86 } 87 } 88 if (n == 0) { 89 continue; 90 } 91 if (n >= 8) { 92 this.treeifyBin((Node<K, V>[])array, n2); 93 } 94 //上述b為是否找不到元素 95 if (!b) { 96 return (V)o; 97 } 98 break; 99 } 100 } 101 } 102 } 103 //上一個操作中,hash值能對應表,並且找到值之后就返回了 104 //這里只有找不到值,需要重賦值才執行到 105 if (o != null) { 106 this.addCount(1L, n); 107 } 108 return (V)o; 109 }
這里網上找不到代碼,用rt.jar反編譯的代碼代替
如果找到的表預備擴容,會先執行helpTransfer
在此之后,如果是添加元素而非單純的查找元素,就會執行addCount操作,這倆操作都會擴容
擴容的內部邏輯
讀了一下transfer代碼,了解了很多東西。
首先是為什么初始容量必須為2的整數次方,並且擴容是2倍2倍地擴:使擴容前的單個節點無需與其他節點交互,只控制自己的節點和跳過去的新節點。
例如hash表有8個,要擴容為16個,那么之前放在7表的數據,可能對16取模會余15,也可能還是7;其他7種元素只能分配到7和15以外的0-15余數;這樣的話,只需把部分數據往15表上移,保留一部分到原表中。
並且數程序會仔細判定數據的插入順序,這使得原來數據的插入順序擴容之后不會打亂。
再者還有為什么有ForwardingNode和helpTransfer():單個擴容線程的擴容速度會很慢,可能有線程需要插入尾端數據,但是那個擴容線程在頭端。這樣的話,給對應hash表設置靈活的擴容機制比較重要。讀線程不需修改數據,直接把數據讀了就是;寫線程要考慮新插入的數據位置,讓這個線程協助擴容,再完成插入操作的話這個線程的寫操作不會堵塞很久。
這就是ForwardingNode節點和helpTransfer()的作用。
1 private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) { 2 int n = tab.length, stride; 3 //計算每次遷移的node個數(MIN_TRANSFER_STRIDE該值作為下限,以避免擴容線程過多) 4 if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE) 5 // 確保每次遷移的node個數不少於16個 6 stride = MIN_TRANSFER_STRIDE; 7 // nextTab為擴容中的臨時table 8 if (nextTab == null) { 9 try { 10 //擴容一倍 11 @SuppressWarnings("unchecked") 12 // 1. 新建一個 node 數組,容量為之前的兩倍 13 Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1]; 14 nextTab = nt; 15 } catch (Throwable ex) { // try to copy with OOME 16 sizeCtl = Integer.MAX_VALUE; 17 return; 18 } 19 nextTable = nextTab; 20 // transferIndex為擴容復制過程中的桶首節點遍歷索引 21 // 所以從n開始,表示從后向前遍歷 22 transferIndex = n; 23 } 24 int nextn = nextTab.length; 25 // ForwardingNode是Node節點的直接子類,是擴容過程中的特殊桶首節點 26 // 該類中沒有key,value,next 27 // hash值為特定的-1 28 // 附加Node<K,V>[] nextTable變量指向擴容中的nextTab 29 // 在find方法中,將擴容中的查詢操作導入到nextTab上 30 //2. 新建forwardingNode引用,在之后會用到 31 ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab); 32 boolean advance = true; 33 // 循環的關鍵變量,判斷是否已經擴容完成,完成就 return , 退出循環 34 boolean finishing = false; 35 //【1】逆序遷移已經獲取到的hash桶集合,如果遷移完畢,則更新transferIndex, 36 // 獲取下一批待遷移的hash桶 37 //【2】如果transferIndex=0,表示所以hash桶均被分配,將i置為-1, 38 // 准備退出transfer方法 39 for (int i = 0, bound = 0;;) { 40 Node<K,V> f; int fh; 41 // 3. 確定遍歷中的索引i(更新待遷移的hash桶索引) 42 // 循環的關鍵 i , i-- 操作保證了倒敘遍歷數組 43 while (advance) { 44 int nextIndex, nextBound; 45 // 更新遷移索引i 46 if (--i >= bound || finishing) 47 advance = false; 48 // transferIndex = 0表示table中所有數組元素都已經有其他線程負責擴容 49 // nextIndex=transferIndex=n=tab.length(默認16) 50 else if ((nextIndex = transferIndex) <= 0) { 51 // transferIndex<=0表示已經沒有需要遷移的hash桶, 52 // 將i置為-1,線程准備退出 53 i = -1; 54 advance = false; 55 } 56 //cas無鎖算法設置 transferIndex = transferIndex - stride 57 // 嘗試更新transferIndex,獲取當前線程執行擴容復制的索引區間 58 // 更新成功,則當前線程負責完成索引為(nextBound,nextIndex)之間的桶首節點擴容 59 //當遷移完bound這個桶后,嘗試更新transferIndex,獲取下一批待遷移的hash桶 60 else if (U.compareAndSwapInt 61 (this, TRANSFERINDEX, nextIndex, 62 nextBound = (nextIndex > stride ? 63 nextIndex - stride : 0))) { 64 bound = nextBound; 65 i = nextIndex - 1; 66 advance = false; 67 } 68 } //退出transfer 69 //4.將原數組中的元素復制到新數組中去 70 //4.5 for循環退出,擴容結束修改sizeCtl屬性 71 // i<0 說明已經遍歷完舊的數組tab;i>=n什么時候有可能呢?在下面看到i=n,所以目前i最大應該是n吧 72 // i+n>=nextn,nextn=nextTab.length,所以如果滿足i+n>=nextn說明已經擴容完成 73 if (i < 0 || i >= n || i + n >= nextn) { 74 int sc; 75 if (finishing) { // a 76 //最后一個遷移的線程,recheck后,做收尾工作,然后退出 77 nextTable = null; 78 table = nextTab; 79 // 擴容成功,設置新sizeCtl,仍然為總大小的0.75 80 sizeCtl = (n << 1) - (n >>> 1); 81 return; 82 } 83 84 // 第一個擴容的線程,執行transfer方法之前,會設置 sizeCtl = 85 // (resizeStamp(n) << RESIZE_STAMP_SHIFT) + 2) 86 // 后續幫其擴容的線程,執行transfer方法之前,會設置 sizeCtl = sizeCtl+1 87 // 每一個退出transfer的方法的線程,退出之前,會設置 sizeCtl = sizeCtl-1 88 // 那么最后一個線程退出時: 89 // 必然有sc == (resizeStamp(n) << RESIZE_STAMP_SHIFT) + 2), 90 // 即 (sc - 2) == resizeStamp(n) << RESIZE_STAMP_SHIFT 91 92 if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) { 93 // 如果有多個線程進行擴容,那么這個值在第二個線程以后就不會相等,因為 94 // sizeCtl 已經被減1了,所以后面的線程只能直接返回, 95 // 始終保證只有一個線程執行了a(上面的注釋a) 96 if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT) 97 return; 98 // finishing 和 advance 保證線程已經擴容完成了可以退出循環 99 finishing = advance = true; 100 //最后退出的線程要重新check下是否全部遷移完畢 101 i = n; 102 } 103 } 104 // 當前table節點為空,不需要復制,直接放入ForwardingNode 105 //4.1 當前數組中第i個元素為null,用CAS設置成特殊節點forwardingNode(可以理解成占位符) 106 //如果 tab[i] 為 null,那么就把 fwd 插入到 tab[i],表明這個節點已經處理過了 107 else if ((f = tabAt(tab, i)) == null) 108 advance = casTabAt(tab, i, null, fwd); 109 // 當前table節點已經是ForwardingNode 110 // 表示已經被其他線程處理了,則直接往前遍歷 111 // 通過CAS讀寫ForwardingNode節點狀態,達到多線程互斥處理 112 // 4.2 如果遍歷到ForwardingNode節點說明這個點已經被處理過了直接跳過 113 // 這里是控制並發擴容的核心 114 // 如果 f.hash=-1 的話說明該節點為 ForwardingNode,說明該節點已經處理過了 115 else if ((fh = f.hash) == MOVED) 116 advance = true; 117 //遷移node節點 118 else { 119 // 鎖住當前桶首節點 120 synchronized (f) { 121 if (tabAt(tab, i) == f) { 122 Node<K,V> ln, hn; 123 // 鏈表節點復制(鏈表遷移) 124 if (fh >= 0) { 125 // 4.3 處理當前節點為鏈表的頭結點的情況,構造兩個鏈表,一個是原鏈表 126 // 另一個是原鏈表的反序排列 127 int runBit = fh & n; 128 Node<K,V> lastRun = f; 129 //將node鏈表,分成2個新的node鏈表 130 // 這邊還對鏈表進行遍歷,這邊的算法和hashMap的算法又不一樣了,對半拆分 131 // 把鏈表拆分為,hash&n 等於0和不等於0的,然后分別放在新表的i和i+n位置 132 // 此方法同 HashMap 的 resize 133 for (Node<K,V> p = f.next; p != null; p = p.next) { 134 int b = p.hash & n; 135 if (b != runBit) { 136 runBit = b; 137 lastRun = p; 138 } 139 } 140 if (runBit == 0) { 141 ln = lastRun; 142 hn = null; 143 } 144 else { 145 hn = lastRun; 146 ln = null; 147 } 148 for (Node<K,V> p = f; p != lastRun; p = p.next) { 149 int ph = p.hash; K pk = p.key; V pv = p.val; 150 if ((ph & n) == 0) 151 ln = new Node<K,V>(ph, pk, pv, ln); 152 else 153 hn = new Node<K,V>(ph, pk, pv, hn); 154 } 155 //將新node鏈表賦給nextTab 156 //在nextTable的i位置上插入一個鏈表 157 setTabAt(nextTab, i, ln); 158 //在nextTable的i+n的位置上插入另一個鏈表 159 setTabAt(nextTab, i + n, hn); 160 // 擴容成功后,設置ForwardingNode節點 161 //在table的i位置上插入forwardNode節點表示已經處理過該節點 162 // 把已經替換的節點的舊tab的i的位置用fwd替換,fwd包含nextTab 163 setTabAt(tab, i, fwd); 164 //設置advance為true 返回到上面的while循環中 就可以執行i--操作 165 advance = true; 166 } 167 // 紅黑樹節點復制(紅黑樹遷移) 168 //4.4 處理當前節點是TreeBin時的情況,操作和上面的類似 169 else if (f instanceof TreeBin) { 170 TreeBin<K,V> t = (TreeBin<K,V>)f; 171 TreeNode<K,V> lo = null, loTail = null; 172 TreeNode<K,V> hi = null, hiTail = null; 173 int lc = 0, hc = 0; 174 for (Node<K,V> e = t.first; e != null; e = e.next) { 175 int h = e.hash; 176 TreeNode<K,V> p = new TreeNode<K,V> 177 (h, e.key, e.val, null, null); 178 //可看出本節點擴容之后應該放低位節點 179 if ((h & n) == 0) { 180 if ((p.prev = loTail) == null) 181 lo = p; 182 else 183 loTail.next = p; 184 loTail = p; 185 ++lc; 186 } 187 //擴容之后應該放高位節點 188 else { 189 if ((p.prev = hiTail) == null) 190 hi = p; 191 else 192 hiTail.next = p; 193 hiTail = p; 194 ++hc; 195 } 196 } 197 // 判斷擴容后是否還需要紅黑樹 198 ln = (lc <= UNTREEIFY_THRESHOLD) ? untreeify(lo) : 199 (hc != 0) ? new TreeBin<K,V>(lo) : t; 200 hn = (hc <= UNTREEIFY_THRESHOLD) ? untreeify(hi) : 201 (lc != 0) ? new TreeBin<K,V>(hi) : t; 202 setTabAt(nextTab, i, ln); 203 setTabAt(nextTab, i + n, hn); 204 // 擴容成功后,設置ForwardingNode節點 205 setTabAt(tab, i, fwd); 206 advance = true; 207 } 208 } 209 } 210 } 211 } 212 }
