這個ConcurrentHashmap的設計很是精妙,若是有疑問的地方,歡迎你們在評論區進行激烈討論!java
1、靜態工具方法node
1 private static final int tableSizeFor(int c) { 2 int n = c - 1; 3 n |= n >>> 1; 4 n |= n >>> 2; 5 n |= n >>> 4; 6 n |= n >>> 8; 7 n |= n >>> 16; 8 return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; 9 }
此方法是對給定的int型數據c,返回一個值(好比叫x),則x知足x >=c且x是2的整數次冪。數組
首先爲何先將c-1,咱們等下再說,先解釋下從代碼第3行到第7行的意思,第三行的意思是先將n與n無符號右移1位後的值作「或」運算,而後將值再賦給n,以後的以此類推。dom
爲何最後只到了右移16位呢?由於int數據在內存中只有32位。通過這一系列操做,就保證了n的二進制表示中將第一個出現1的位置的後面所有設置爲1。而後再返回n+1就保證了ide
大於c而且是2的整數次冪。而後再解釋下爲何開頭先將c減去1,由於若是c原本就是2的m次冪的話,咱們使用一樣的方法最後會獲得2的m+1次冪的結果。函數
2、初始化table:工具
1 private final Node<K,V>[] initTable() { 2 Node<K,V>[] tab; int sc; 3 while ((tab = table) == null || tab.length == 0) { 4 if ((sc = sizeCtl) < 0) 5 Thread.yield(); // lost initialization race; just spin 6 else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) { 7 try { 8 if ((tab = table) == null || tab.length == 0) { 9 int n = (sc > 0) ? sc : DEFAULT_CAPACITY; 10 @SuppressWarnings("unchecked") 11 Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n]; 12 table = tab = nt; 13 sc = n - (n >>> 2); 14 } 15 } finally { 16 sizeCtl = sc; 17 } 18 break; 19 } 20 } 21 return tab; 22 }
當多個線程同事執行第6行時,只會有一個返回true。compareAndSwapInt方法會將堆上的字段sizeCtl改成-1.這樣其餘線程會繼續在while循環中一直處於第4行的判斷內。直到線程將使用sizeCtl將table初始化完。在初始化table後,sizeCtl會修改成下次須要擴容的閾值,即table容量乘以負載因子(n*0.75),這裏使用位移的方法(如第13行)。從這裏能夠看出,其實這個負載因子是固定不變的。構造函數中的loadFactor,僅僅影響table初始化的容量:this
1 public ConcurrentHashMap(int initialCapacity, 2 float loadFactor, int concurrencyLevel) { 3 if (!(loadFactor > 0.0f) || initialCapacity < 0 || concurrencyLevel <= 0) 4 throw new IllegalArgumentException(); 5 if (initialCapacity < concurrencyLevel) // Use at least as many bins 6 initialCapacity = concurrencyLevel; // as estimated threads 7 long size = (long)(1.0 + (long)initialCapacity / loadFactor); 8 int cap = (size >= (long)MAXIMUM_CAPACITY) ? 9 MAXIMUM_CAPACITY : tableSizeFor((int)size); 10 this.sizeCtl = cap; 11 }
3、新增數據(put方法)spa
1 final V putVal(K key, V value, boolean onlyIfAbsent) { 2 if (key == null || value == null) throw new NullPointerException(); 3 int hash = spread(key.hashCode()); 4 int binCount = 0; 5 for (Node<K,V>[] tab = table;;) { 6 Node<K,V> f; int n, i, fh; 7 if (tab == null || (n = tab.length) == 0) 8 tab = initTable(); 9 else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) { 10 if (casTabAt(tab, i, null, 11 new Node<K,V>(hash, key, value, null))) 12 break; // no lock when adding to empty bin 13 } 14 else if ((fh = f.hash) == MOVED) 15 tab = helpTransfer(tab, f); 16 else { 17 V oldVal = null; 18 synchronized (f) { 19 if (tabAt(tab, i) == f) { 20 if (fh >= 0) { 21 binCount = 1; 22 for (Node<K,V> e = f;; ++binCount) { 23 K ek; 24 if (e.hash == hash && 25 ((ek = e.key) == key || 26 (ek != null && key.equals(ek)))) { 27 oldVal = e.val; 28 if (!onlyIfAbsent) 29 e.val = value; 30 break; 31 } 32 Node<K,V> pred = e; 33 if ((e = e.next) == null) { 34 pred.next = new Node<K,V>(hash, key, 35 value, null); 36 break; 37 } 38 } 39 } 40 else if (f instanceof TreeBin) { 41 Node<K,V> p; 42 binCount = 2; 43 if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key, 44 value)) != null) { 45 oldVal = p.val; 46 if (!onlyIfAbsent) 47 p.val = value; 48 } 49 } 50 } 51 } 52 if (binCount != 0) { 53 if (binCount >= TREEIFY_THRESHOLD) 54 treeifyBin(tab, i); 55 if (oldVal != null) 56 return oldVal; 57 break; 58 } 59 } 60 } 61 addCount(1L, binCount); 62 return null; 63 }
方法的主要邏輯是:在key和數組長度計算出的值來肯定在數組中插入的位置,若是此位置原來沒有節點則構造一個節點插入該位置,若是之前有節點,則在節點所在的鏈表添加新的節點,若是此位置的節點是一顆樹,則在樹上添加新的節點。若是插入後的節點數量大於TREEIFY_THRESHOLD,則將該節點轉化爲樹形結構。(該樹爲一顆紅黑樹)。其中第15行方法helpTransfer,第54行treeifyBin和第61行addCount方法,將會在後面進行說明。值得注意的是:在方法treefyBin中,會判斷若是table的長度小於MIN_TREEIFY_CAPACITY的話,則不會將節點構形成樹,而是將table擴容。線程
4、樹化
1 private final void treeifyBin(Node<K,V>[] tab, int index) { 2 Node<K,V> b; int n, sc; 3 if (tab != null) { 4 if ((n = tab.length) < MIN_TREEIFY_CAPACITY) 5 tryPresize(n << 1); 6 else if ((b = tabAt(tab, index)) != null && b.hash >= 0) { 7 synchronized (b) { 8 if (tabAt(tab, index) == b) { 9 TreeNode<K,V> hd = null, tl = null; 10 for (Node<K,V> e = b; e != null; e = e.next) { 11 TreeNode<K,V> p = 12 new TreeNode<K,V>(e.hash, e.key, e.val, 13 null, null); 14 if ((p.prev = tl) == null) 15 hd = p; 16 else 17 tl.next = p; 18 tl = p; 19 } 20 setTabAt(tab, index, new TreeBin<K,V>(hd)); 21 } 22 } 23 } 24 } 25 }
真正的樹化動做是在第20行的new TreeBin<K,V>(hd)此構造方法中進行的。其中TreeBin類是一個紅黑樹結構。
5、擴容方法(transfer)
1 private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) { 2 int n = tab.length, stride; 3 if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE) 4 stride = MIN_TRANSFER_STRIDE; // subdivide range 5 if (nextTab == null) { // initiating 6 try { 7 @SuppressWarnings("unchecked") 8 Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1]; 9 nextTab = nt; 10 } catch (Throwable ex) { // try to cope with OOME 11 sizeCtl = Integer.MAX_VALUE; 12 return; 13 } 14 nextTable = nextTab; 15 transferIndex = n; 16 } 17 int nextn = nextTab.length; 18 ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab); 19 boolean advance = true; 20 boolean finishing = false; // to ensure sweep before committing nextTab 21 for (int i = 0, bound = 0;;) { 22 Node<K,V> f; int fh; 23 while (advance) { 24 int nextIndex, nextBound; 25 if (--i >= bound || finishing) 26 advance = false; 27 else if ((nextIndex = transferIndex) <= 0) { 28 i = -1; 29 advance = false; 30 } 31 else if (U.compareAndSwapInt 32 (this, TRANSFERINDEX, nextIndex, 33 nextBound = (nextIndex > stride ? 34 nextIndex - stride : 0))) { 35 bound = nextBound; 36 i = nextIndex - 1; 37 advance = false; 38 } 39 } 40 if (i < 0 || i >= n || i + n >= nextn) { 41 int sc; 42 if (finishing) { 43 nextTable = null; 44 table = nextTab; 45 sizeCtl = (n << 1) - (n >>> 1); 46 return; 47 } 48 if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) { 49 if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT) 50 return; 51 finishing = advance = true; 52 i = n; // recheck before commit 53 } 54 } 55 else if ((f = tabAt(tab, i)) == null) 56 advance = casTabAt(tab, i, null, fwd); 57 else if ((fh = f.hash) == MOVED) 58 advance = true; // already processed 59 else { 60 synchronized (f) { 61 if (tabAt(tab, i) == f) { 62 Node<K,V> ln, hn; 63 if (fh >= 0) { 64 int runBit = fh & n; 65 Node<K,V> lastRun = f; 66 for (Node<K,V> p = f.next; p != null; p = p.next) { 67 int b = p.hash & n; 68 if (b != runBit) { 69 runBit = b; 70 lastRun = p; 71 } 72 } 73 if (runBit == 0) { 74 ln = lastRun; 75 hn = null; 76 } 77 else { 78 hn = lastRun; 79 ln = null; 80 } 81 for (Node<K,V> p = f; p != lastRun; p = p.next) { 82 int ph = p.hash; K pk = p.key; V pv = p.val; 83 if ((ph & n) == 0) 84 ln = new Node<K,V>(ph, pk, pv, ln); 85 else 86 hn = new Node<K,V>(ph, pk, pv, hn); 87 } 88 setTabAt(nextTab, i, ln); 89 setTabAt(nextTab, i + n, hn); 90 setTabAt(tab, i, fwd); 91 advance = true; 92 } 93 else if (f instanceof TreeBin) { 94 TreeBin<K,V> t = (TreeBin<K,V>)f; 95 TreeNode<K,V> lo = null, loTail = null; 96 TreeNode<K,V> hi = null, hiTail = null; 97 int lc = 0, hc = 0; 98 for (Node<K,V> e = t.first; e != null; e = e.next) { 99 int h = e.hash; 100 TreeNode<K,V> p = new TreeNode<K,V> 101 (h, e.key, e.val, null, null); 102 if ((h & n) == 0) { 103 if ((p.prev = loTail) == null) 104 lo = p; 105 else 106 loTail.next = p; 107 loTail = p; 108 ++lc; 109 } 110 else { 111 if ((p.prev = hiTail) == null) 112 hi = p; 113 else 114 hiTail.next = p; 115 hiTail = p; 116 ++hc; 117 } 118 } 119 ln = (lc <= UNTREEIFY_THRESHOLD) ? untreeify(lo) : 120 (hc != 0) ? new TreeBin<K,V>(lo) : t; 121 hn = (hc <= UNTREEIFY_THRESHOLD) ? untreeify(hi) : 122 (lc != 0) ? new TreeBin<K,V>(hi) : t; 123 setTabAt(nextTab, i, ln); 124 setTabAt(nextTab, i + n, hn); 125 setTabAt(tab, i, fwd); 126 advance = true; 127 } 128 } 129 } 130 } 131 } 132 }
整體邏輯:
1.若是nextTable爲null則初始化一個。(容量爲之前的2倍)
2.而後將table中的節點按MIN_TRANSFER_STRIDE分爲多個區間
3.對每一個區間的節點數據進行轉移,轉之後在table中將節點(即便該節點爲null)置爲ForwardingNode,標記爲已轉移。
4.最後table = nextTable。
須要注意的細節:
問:爲何要分爲多個區間?
答:能夠容許多個線程同時進行擴容不一樣的區間而不受其餘線程影響。關鍵代碼位置:第27行到第37行。
第63行條件判斷開始處理鏈表節點的轉移:
runBit記錄節點的hash屬性與原數組長度n的「&」運算結果,lastRun記錄的是鏈表中最後面位置中節點的runBit值相同的子鏈表的頭結點。
由於n是原數組的長度,而數組長度必須是2的整數次冪(n == 2的x次方),因此n的二進制表示中,只有第x+1位是1,其餘全爲0。因此若是hash&n == 0,則表示hash中第x+1位爲0,不然爲1.
咱們如今假設要轉移的節點(node)所在數組中的位置爲i,則i == node.hash&(n-1),若是node.hash&n == 0的話,那麼node.hash&(2n-1) == node.hash&(n-1) == i。即擴容後node的位置不變。
若是node.hash&n > 0的話,則node.hash&(2n-1) == i + n。即擴容後node的位置向後移動n位。
上面的第64行到第87行是將當前節點分爲2個子鏈表,分別是ln爲hash&n == 0的子鏈表 和hn爲hash&n > 0的子鏈表。根據咱們以前討論的結果ln應該還在當前的位置i,而hn則應該向後移動n位,即在i+n位置。
而後將舊數組的當前節點i位置用一個標誌節點來標記此位置已經轉移了。
轉移樹形結構的節點與鏈表相似,由於TreeNode是Node的子類,其自己也是一個鏈表,能夠經過next屬性遍歷整個樹。
再看下sizeCtl這個實例變量:
1 private transient volatile int sizeCtl;
當它爲負值時,表示正在初始化或者擴容(-1表示初始化);
不然,當table爲null時,它爲默認初始值0,或者保存着table的初始化值;
當table初始化結束時,它保存着下次須要擴容的閾值。
6、計數方法
1 private final void addCount(long x, int check) { 2 CounterCell[] as; long b, s; 3 if ((as = counterCells) != null || 4 !U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) { 5 CounterCell a; long v; int m; 6 boolean uncontended = true; 7 if (as == null || (m = as.length - 1) < 0 || 8 (a = as[ThreadLocalRandom.getProbe() & m]) == null || 9 !(uncontended = 10 U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) { 11 fullAddCount(x, uncontended); 12 return; 13 } 14 if (check <= 1) 15 return; 16 s = sumCount(); 17 } 18 if (check >= 0) { 19 Node<K,V>[] tab, nt; int n, sc; 20 while (s >= (long)(sc = sizeCtl) && (tab = table) != null && 21 (n = tab.length) < MAXIMUM_CAPACITY) { 22 int rs = resizeStamp(n); 23 if (sc < 0) { 24 if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 || 25 sc == rs + MAX_RESIZERS || (nt = nextTable) == null || 26 transferIndex <= 0) 27 break; 28 if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) 29 transfer(tab, nt); 30 } 31 else if (U.compareAndSwapInt(this, SIZECTL, sc, 32 (rs << RESIZE_STAMP_SHIFT) + 2)) 33 transfer(tab, null); 34 s = sumCount(); 35 } 36 } 37
該方法涉及的兩個實例變量baseCount和counterCells是用來保存當前table中有多少數據的。
第17行以前是計算數據個數的,以後是檢查是否須要擴容或者若是正在擴容則參與到擴容中。而後從新檢查看看是否須要繼續擴容。
其中的第24行判斷,是錯誤的,在好幾個方法中用到此種判斷的都是一樣的錯誤,請注意(這是jdk的一個BUG,在如今的已發佈的JDK8-JDK11版本中都未解決,估計之後在JDK12發佈版本中會fix掉,如今的openJDK12中已經fix了,參考https://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8214427),其中的sc==rs + 1 || sc == rs + MAX_RESIZES應該改成sc == (rs << RESIZE_STAMP_SHIFT)+ 1 || sc == (rs << RESIZE_STAMP_SHIFT) + MAX_RESIZES,
用來判斷擴容是否結束和擴容線程是否達到最大值。能夠看下第31行,表示初始化擴容,因此開始擴容時, sizeCtl == (rs << RESIZE_STAMP_SHIFT) + 2,由於每一個擴容線程在進行幫助擴容時都會使用CAS將sizeCtl+1,而後進入transfer,接着在transfer方法中,每當一個擴容線程結束時都會將sizeCtl - 1,因此當全部擴容線程結束時sizeCtl == (rs << RESIZE_STAMP_SHIFT)+ 1,由於第一個進入擴容的線程沒有將sizeCtl+1.
7、最後看下size計算table中數據的個數的方法
1 public int size() { 2 long n = sumCount(); 3 return ((n < 0L) ? 0 : 4 (n > (long)Integer.MAX_VALUE) ? Integer.MAX_VALUE : 5 (int)n); 6 } 7 8 9 10 final long sumCount() { 11 CounterCell[] as = counterCells; CounterCell a; 12 long sum = baseCount; 13 if (as != null) { 14 for (int i = 0; i < as.length; ++i) { 15 if ((a = as[i]) != null) 16 sum += a.value; 17 } 18 } 19 return sum; 20 }
由於size中調用sumCount方法來間接計算個數,因此直接看sumCount方法。
它是將baseCount和counterCells中每一個節點的value屬性值累加在一塊兒獲得最後的數量。