JDK 1.8 源碼解析 ConcurrentHashMap
阿新 • • 發佈:2018-03-03
進一步 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