JDK 1.8 源码解析 ConcurrentHashMap

Posted wjq2017

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JDK 1.8 源码解析 ConcurrentHashMap相关的知识,希望对你有一定的参考价值。

  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的主要内容,如果未能解决你的问题,请参考以下文章

JDK 1.8 源码解析 HashSet

JDK 1.8 源码解析 ConcurrentHashMap

JDK 1.8 源码解析 StringStringBuilder和StringBuffer的异同

Java深入研究9HashMap源码解析(jdk 1.8)

JDK1.8JDK1.8集合源码阅读——TreeMap

Objects源码解析