1. 程式人生 > >JDK 1.8 源碼解析 ConcurrentHashMap

JDK 1.8 源碼解析 ConcurrentHashMap

進一步 equal 保存數據 threshold 時間復雜度 保存 color span 完成

  JDK 1.7中ConcurrentHashMap

  基本結構:

  技術分享圖片

  每一個segment都是一個HashEntry<K,V>[] table, table中的每一個元素本質上都是一個HashEntry的單向隊列。比如table[3]為首結點,table[3]->next為結點1,之後為結點2,依次類推。

 1 public class ConcurrentHashMap<K, V> extends AbstractMap<K, V>
 2         implements ConcurrentMap<K, V>, Serializable {
3 4 // 將整個map分成幾個小的hashmap,每個segment都是一個鎖 5 final Segment<K,V>[] segments; 6 7 // 本質上,Segment類是一個小的hashmap,裏面table數組存儲了各個結點的數據,繼承了ReentrantLock, 可以作為互斥鎖使用 8 static final class Segment<K,V> extends ReentrantLock implements Serializable { 9 transient volatile HashEntry<K,V>[] table;
10 transient int count; 11 } 12 13 // 基本結點,存儲Key和Value值 14 static final class HashEntry<K,V> { 15 final int hash; 16 final K key; 17 volatile V value; 18 volatile HashEntry<K,V> next; 19 } 20 }

  

  JDK 1.8的改進:

  1 取消segments屬性,通過transient volatile HashEntry<K, V>[] table(即桶數組)來保存數據,桶數組元素作為鎖,相當於對每一行數據加鎖,進一步減小鎖粒度。

  2 桶數組+單向鏈表的數據結構變為桶數組+單鏈表+紅黑樹。對於散列表,如果hash之後散列很均勻,則桶數組中每個數組元素指向的鏈表長度是0或1。當結點數量較大時,在單鏈表中查找某個結點的時間復雜度是O(n);對於結點數量超過8的鏈表,轉換成紅黑樹,時間復雜度是O(log2n),提高性能。

  3 新增屬性transient volatile CounterCell[] counterCells,用於統計每個桶中鍵值對數量,可以更快獲取所有鍵值對數量。

  putVal方法:

 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         // 如果table為空,初始化;否則,根據hash值計算得到數組索引i,如果tab[i]為空,直接新建結點Node即可。註:tab[i]實質為鏈表或者紅黑樹的首結點。
 8         if (tab == null || (n = tab.length) == 0)
 9             tab = initTable();
10         else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
11             if (casTabAt(tab, i, null,
12                          new Node<K,V>(hash, key, value, null)))
13                 break;                   // no lock when adding to empty bin
14         }
15         // 如果tab[i]不為空並且hash值為MOVED,說明該鏈表正在進行transfer操作,返回擴容完成後的table。
16         else if ((fh = f.hash) == MOVED)
17             tab = helpTransfer(tab, f);
18         else {
19             V oldVal = null;
20             // 針對首個結點進行加鎖操作,而不是segment,進一步減少線程沖突
21             synchronized (f) {
22                 if (tabAt(tab, i) == f) {
23                     if (fh >= 0) {
24                         binCount = 1;
25                         for (Node<K,V> e = f;; ++binCount) {
26                             K ek;
27                             // 如果在鏈表中找到值為key的結點e,直接設置e.val = value即可。
28                             if (e.hash == hash &&
29                                 ((ek = e.key) == key ||
30                                  (ek != null && key.equals(ek)))) {
31                                 oldVal = e.val;
32                                 if (!onlyIfAbsent)
33                                     e.val = value;
34                                 break;
35                             }
36                             // 如果沒有找到值為key的結點,直接新建Node並加入鏈表即可。
37                             Node<K,V> pred = e;
38                             if ((e = e.next) == null) {
39                                 pred.next = new Node<K,V>(hash, key,
40                                                           value, null);
41                                 break;
42                             }
43                         }
44                     }
45                     // 如果首結點為TreeBin類型,說明為紅黑樹結構,執行putTreeVal操作。
46                     else if (f instanceof TreeBin) {
47                         Node<K,V> p;
48                         binCount = 2;
49                         if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
50                                                        value)) != null) {
51                             oldVal = p.val;
52                             if (!onlyIfAbsent)
53                                 p.val = value;
54                         }
55                     }
56                 }
57             }
58             if (binCount != 0) {
59                 // 如果結點數>=8,那麽轉換鏈表結構為紅黑樹結構。
60                 if (binCount >= TREEIFY_THRESHOLD)
61                     treeifyBin(tab, i);
62                 if (oldVal != null)
63                     return oldVal;
64                 break;
65             }
66         }
67     }
68     // 計數增加1,有可能觸發transfer操作(擴容)。
69     addCount(1L, binCount);
70     return null;
71 }

  

  參考資料

  Java並發編程總結4——ConcurrentHashMap在jdk1.8中的改進

JDK 1.8 源碼解析 ConcurrentHashMap