ConcurrentHashmap源碼好好給你說明白

這個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屬性值累加在一塊兒獲得最後的數量。

相關文章
相關標籤/搜索