JAVA-JDK1.8-ConCurrentHashMap-源码并且debug说明

Posted 伯安知心

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JAVA-JDK1.8-ConCurrentHashMap-源码并且debug说明相关的知识,希望对你有一定的参考价值。

概述

在上述的随笔中已经介绍了JDK1.7版本的ConCurrentHashMap源码和测试了,现在这篇随笔主要介绍JDK1.8版本的ConCurrentHashMap,这个版本抛弃了分段锁的实现,直接采用CAS+synchronized保证并发更新的安全性,底层采用数组+链表+红黑树的存储结构。其包含核心静态内部类Node<K,V>[],数组来保存添加到map中的键值对,而在同一个数组位置是通过链表和红黑树的形式来保存的。但是这个数组只有在第一次添加元素的时候才会初始化,否则只初始化一个ConCurrentHashMap对象,还设定了一个sizeCtl变量,这个变量是用来判断对象的一些状态和是否需要扩容。

第一次添加元素的时候,默认初期长度为16,当往map中继续添加元素的时候,通过 hash值跟数组长度取与来决定放在数组的哪个位置,如果出现放在同一个位置的时候,优先以链表的形式存放,在同一个位置的个数又达到了8个以上,如果数 组的长度还小于64的时候,则会扩容数组。如果数组的长度大于等于64了的话,在会将该节点的链表转换成树。

通过扩容数组的方式来把这些节点给分散开。然后将这些元素复制到扩容后的新的数组中,同 一个链表中的元素通过hash值的数组长度位来区分,是还是放在原来的位置还是放到扩容的长度的相同位置去 。在扩容完成之后,如果某个节点的是树,同时现在该节点的个数又小于等于6个了,则会将该树转为链表,取元素的时候,相对来说比较简单,通过计算hash来确定该元素在数组的哪个位置,然后在通过遍历链表或树来判断key和key的hash,取出value值。

首先看一下数据结构的截图:

从上图所示:数据结构采用数组+链表+红黑树的方式实现,当链表中的bucket的节点个数超过8个时,会转换为红黑树的数据结构存储,这样设计也相应提高了读取效率。

java8主要做了如下优化:

1,将Segment抛弃掉了,直接采用Node作为table元素。

2,修改时,不再采用ReentrantLock加锁,直接使用内置synchronized加锁,java8内置锁,比之前版本优化好多,相比ReentrantLock,性能并不差。

3,size方法优化,增加了CounterCell内部类,用于并行计算每个bucket的元素数量。

ConCurrentHashMap的几个重要类

Java8中ConCurrentHashMap增加了很多内部类来支持一些操作和优化性能,先介绍几个比较重要的属性。

 1 public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
 2     implements ConcurrentMap<K,V>, Serializable {
 3     // table最大容量,为2的幂次方
 4     private static final int MAXIMUM_CAPACITY = 1 << 30;
 5     // 默认table初始容量大小
 6     private static final int DEFAULT_CAPACITY = 16;
 7     // 默认支持并发更新的线程数量
 8     private static final int DEFAULT_CONCURRENCY_LEVEL = 16;
 9     // table的负载因子
10     private static final float LOAD_FACTOR = 0.75f;
11     // 链表转换为红黑树的节点数阈值,超过这个值,链表转换为红黑树
12     static final int TREEIFY_THRESHOLD = 8;
13     // 在扩容期间,由红黑树转换为链表的阈值,小于这个值,resize期间红黑树就会转为链表
14     static final int UNTREEIFY_THRESHOLD = 6;
15     // 转为红黑树时,红黑树中节点的最小个数
16     static final int MIN_TREEIFY_CAPACITY = 64;
17     // 扩容时,并发转移节点(transfer方法)时,每次转移的最小节点数
18     private static final int MIN_TRANSFER_STRIDE = 16;
19 
20     // 以下常量定义了特定节点类hash字段的值
21     static final int MOVED     = -1; // ForwardingNode类对象的hash值
22     static final int TREEBIN   = -2; // TreeBin类对象的hash值
23     static final int RESERVED  = -3; // ReservationNode类对象的hash值
24     static final int HASH_BITS = 0x7fffffff; // 普通Node节点的hash初始值
25 
26     // table数组
27     transient volatile Node<K,V>[] table;
28     // 扩容时,下一个容量大小的talbe,用于将原table元素移动到这个table中
29     private transient volatile Node<K,V>[] nextTable;
30     // 基础计数器
31     private transient volatile long baseCount;
32     // table初始容量大小以及扩容容量大小的参数,也用于标识table的状态
33     // 其有几个值来代表也用来代表table的状态:
34     // -1 :标识table正在初始化
35     // - N : 标识table正在进行扩容,并且有N - 1个线程一起在进行扩容
36     // 正数:初始table的大小,如果值大于初始容量大小,则表示扩容后的table大小。
37     private transient volatile int sizeCtl;
38     // 扩容时,下一个节点转移的bucket索引下标
39     private transient volatile int transferIndex;
40     // 一种自旋锁,是专为防止多处理器并发而引入的一种锁,用于创建CounterCells时使用,
41     // 主要用于size方法计数时,有并发线程插入而计算修改的节点数量,
42     // 这个数量会与baseCount计数器汇总后得出size的结果。
43     private transient volatile int cellsBusy;
44     // 主要用于size方法计数时,有并发线程插入而计算修改的节点数量,
45     // 这个数量会与baseCount计数器汇总后得出size的结果。
46     private transient volatile CounterCell[] counterCells;
47     // 其他省略
48 }

下面是几个重要的核心内部类。

(1)Node类:存放元素的key,value,hash值,next下一个链表节点的引用。用于bucket为链表时。
 1 static class Node<K,V> implements Map.Entry<K,V> {
 2         final int hash;
 3         final K key;
 4         volatile V val;
 5         volatile Node<K,V> next;
 6 
 7         Node(int hash, K key, V val, Node<K,V> next) {
 8             this.hash = hash;
 9             this.key = key;
10             this.val = val;
11             this.next = next;
12         }
13 
14         public final K getKey()       { return key; }
15         public final V getValue()     { return val; }
16         public final int hashCode()   { return key.hashCode() ^ val.hashCode(); }
17         public final String toString(){ return key + "=" + val; }
18         public final V setValue(V value) {
19             throw new UnsupportedOperationException();
20         }
21 
22         public final boolean equals(Object o) {
23             Object k, v, u; Map.Entry<?,?> e;
24             return ((o instanceof Map.Entry) &&
25                     (k = (e = (Map.Entry<?,?>)o).getKey()) != null &&
26                     (v = e.getValue()) != null &&
27                     (k == key || k.equals(key)) &&
28                     (v == (u = val) || v.equals(u)));
29         }
30 
31         /**
32          * Virtualized support for map.get(); overridden in subclasses.
33          */
34         Node<K,V> find(int h, Object k) {
35             Node<K,V> e = this;
36             if (k != null) {
37                 do {
38                     K ek;
39                     if (e.hash == h &&
40                         ((ek = e.key) == k || (ek != null && k.equals(ek))))
41                         return e;
42                 } while ((e = e.next) != null);
43             }
44             return null;
45         }
46     }
(2)TreeBin:内部属性有root,first节点,以及root节点的锁状态变量lockState,这是一个读写锁的状态。用于存放红黑树的root节点,并用读写锁lockState控制在写操作即将要调整树结构前,先让读线程完成读操作。从链表结构调整为红黑树时,table中索引下标存储的即为TreeBin。
 1 static final class TreeBin<K,V> extends Node<K,V> {
 2         TreeNode<K,V> root;
 3         volatile TreeNode<K,V> first;
 4         volatile Thread waiter;
 5         volatile int lockState;
 6         // values for lockState
 7         static final int WRITER = 1; // set while holding write lock
 8         static final int WAITER = 2; // set when waiting for write lock
 9         static final int READER = 4; // increment value for setting read lock
10 
11         /**
12          * Tie-breaking utility for ordering insertions when equal
13          * hashCodes and non-comparable. We don\'t require a total
14          * order, just a consistent insertion rule to maintain
15          * equivalence across rebalancings. Tie-breaking further than
16          * necessary simplifies testing a bit.
17          */
18         static int tieBreakOrder(Object a, Object b) {
19             int d;
20             if (a == null || b == null ||
21                 (d = a.getClass().getName().
22                  compareTo(b.getClass().getName())) == 0)
23                 d = (System.identityHashCode(a) <= System.identityHashCode(b) ?
24                      -1 : 1);
25             return d;
26         }
27 
28         /**
29          * Creates bin with initial set of nodes headed by b.
30          */
31         TreeBin(TreeNode<K,V> b) {
32             super(TREEBIN, null, null, null);
33             this.first = b;
34             TreeNode<K,V> r = null;
35             for (TreeNode<K,V> x = b, next; x != null; x = next) {
36                 next = (TreeNode<K,V>)x.next;
37                 x.left = x.right = null;
38                 if (r == null) {
39                     x.parent = null;
40                     x.red = false;
41                     r = x;
42                 }
43                 else {
44                     K k = x.key;
45                     int h = x.hash;
46                     Class<?> kc = null;
47                     for (TreeNode<K,V> p = r;;) {
48                         int dir, ph;
49                         K pk = p.key;
50                         if ((ph = p.hash) > h)
51                             dir = -1;
52                         else if (ph < h)
53                             dir = 1;
54                         else if ((kc == null &&
55                                   (kc = comparableClassFor(k)) == null) ||
56                                  (dir = compareComparables(kc, k, pk)) == 0)
57                             dir = tieBreakOrder(k, pk);
58                             TreeNode<K,V> xp = p;
59                         if ((p = (dir <= 0) ? p.left : p.right) == null) {
60                             x.parent = xp;
61                             if (dir <= 0)
62                                 xp.left = x;
63                             else
64                                 xp.right = x;
65                             r = balanceInsertion(r, x);
66                             break;
67                         }
68                     }
69                 }
70             }
71             this.root = r;
72             assert checkInvariants(root);
73         }
74 
75         /**
76          * Acquires write lock for tree restructuring.
77          */
78         private final void lockRoot() {
79             if (!U.compareAndSwapInt(this, LOCKSTATE, 0, WRITER))
80                 contendedLock(); // offload to separate method
81         }
82 
83         /**
84          * Releases write lock for tree restructuring.
85          */
86         private final void unlockRoot() {
87             lockState = 0;
88         }
89 ....略
90 }
(3)TreeNode:红黑树的节点,存放了父节点,左子节点,右子节点的引用,以及红黑节点标识。
 1     static final class TreeNode<K,V> extends Node<K,V> {
 2         TreeNode<K,V> parent;  // red-black tree links
 3         TreeNode<K,V> left;
 4         TreeNode<K,V> right;
 5         TreeNode<K,V> prev;    // needed to unlink next upon deletion
 6         boolean red;
 7 
 8         TreeNode(int hash, K key, V val, Node<K,V> next,
 9                  TreeNode<K,V> parent) {
10             super(hash, key, val, next);
11             this.parent = parent;
12         }
13 
14         Node<K,V> find(int h, Object k) {
15             return findTreeNode(h, k, null);
16         }
17 
18         /**
19          * Returns the TreeNode (or null if not found) for the given key
20          * starting at given root.
21          */
22         final TreeNode<K,V> findTreeNode(int h, Object k, Class<?> kc) {
23             if (k != null) {
24                 TreeNode<K,V> p = this;
25                 do  {
26                     int ph, dir; K pk; TreeNode<K,V> q;
27                     TreeNode<K,V> pl = p.left, pr = p.right;
28                     if ((ph = p.hash) > h)
29                         p = pl;
30                     else if (ph < h)
31                         p = pr;
32                     else if ((pk = p.key) == k || (pk != null && k.equals(pk)))
33                         return p;
34                     else if (pl == null)
35                         p = pr;
36                     else if (pr == null)
37                         p = pl;
38                     else if ((kc != null ||
39                               (kc = comparableClassFor(k)) != null) &&
40                              (dir = compareComparables(kc, k, pk)) != 0)
41                         p = (dir < 0) ? pl : pr;
42                     else if ((q = pr.findTreeNode(h, k, kc)) != null)
43                         return q;
44                     else
45                         p = pl;
46                 } while (p != null);
47             }
48             return null;
49         }
50     }
(4)ForwardingNode:在调用transfer()方法期间,插入bucket头部的节点,主要用来标识在扩容时元素的移动状态,即是否在扩容时还有并发的插入节点,并保证该节点也能够移动到扩容后的表中。
 1     static final class ForwardingNode<K,V> extends Node<K,V> {
 2         final Node<K,V>[] nextTable;
 3         ForwardingNode(Node<K,V>[] tab) {
 4             super(MOVED, null, null, null);
 5             this.nextTable = tab;
 6         }
 7 
 8         Node<K,V> find(int h, Object k) {
 9             // loop to avoid arbitrarily deep recursion on forwarding nodes
10             outer: for (Node<K,V>[] tab = nextTable;;) {
11                 Node<K,V> e; int n;
12                 if (k == null || tab == null || (n = tab.length) == 0 ||
13                     (e = tabAt(tab, (n - 1) & h)) == null)
14                     return null;
15                 for (;;) {
16                     int eh; K ek;
17                     if ((eh = e.hash) == h &&
18                         ((ek = e.key) == k || (ek != null && k.equals(ek))))
19                         return e;
20                     if (eh < 0) {
21                         if (e instanceof ForwardingNode) {
22                             tab = ((ForwardingNode<K,V>)e).nextTable;
23                             continue outer;
24                         }
25                         else
26                             return e.find(h, k);
27                     }
28                     if ((e = e.next) == null)
29                         return null;
30                 }
31             }
32         }
33     }
(5)ReservationNode:占位节点,不存储任何信息,无实际用处,仅用于computeIfAbsent和compute方法中。
1 static final class ReservationNode<K,V> extends Node<K,V> {
2         ReservationNode() {
3             super(RESERVED, null, null, null);
4         }
5 
6         Node<K,V> find(int h, Object k) {
7             return null;
8         }
9     }

 其他比较重要的方法

在ConcurrentHashMap中使用了unSafe方法,通过直接操作内存的方式来保证并发处理的安全性,使用的是硬件的安全机制。

 1     /*
 2      * Volatile access methods are used for table elements as well as
 3      * elements of in-progress next table while resizing.  All uses of
 4      * the tab arguments must be null checked by callers.  All callers
 5      * also paranoically precheck that tab\'s length is not zero (or an
 6      * equivalent check), thus ensuring that any index argument taking
 7      * the form of a hash value anded with (length - 1) is a valid
 8      * index.  Note that, to be correct wrt arbitrary concurrency
 9      * errors by users, these checks must operate on local variables,
10      * which accounts for some odd-looking inline assignments below.
11      * Note that calls to setTabAt always occur within locked regions,
12      * and so in principle require only release ordering, not
13      * full volatile semantics, but are currently coded as volatile
14      * writes to be conservative.
15      */
16     /*
17      * 用来返回节点数组的指定位置的节点的原子操作
18      */
19     @SuppressWarnings("unchecked")
20     static final <K,V> Node<K,V> tabAt(Node<K,V>[] tab, int i) {
21         return (Node<K,V>)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE);
22     }
23 
24     /*
25      * cas原子操作,在指定位置设定值
26      */
27     static final <K,V> boolean casTabAt(Node<K,V>[] tab, int i,
28                                         Node<K,V> c, Node<K,V> v) {
29         return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v);
30     }
31     /*
32      * 原子操作,在指定位置设定值
33      */
34     static final <K,V> void setTabAt(Node<K,V>[] tab, int i, Node<K,V> v) {
35         U.putObjectVolatile(tab, ((long)i << ASHIFT) + ABASE, v);
36     }

ConCurrentHashMap构造方法 

 1     /**
 2      * Creates a new, empty map with the default initial table size (16).
 3      */
 4     public ConcurrentHashMap() {
 5     }
 6 
 7     /**
 8      * Creates a new, empty map with an initial table size
 9      * accommodating the specified number of elements without the need
10      * to dynamically resize.
11      *
12      * @param initialCapacity The implementation performs internal
13      * sizing to accommodate this many elements.
14      * @throws以上是关于JAVA-JDK1.8-ConCurrentHashMap-源码并且debug说明的主要内容,如果未能解决你的问题,请参考以下文章

211java-jdk安装记录

Java-JDK配环境,eclipse工具认识

java-jdk7新特性

web service003——通过java-jdk版本发布web service02

JAVA-JDK1.8-ConCurrentHashMap-源码并且debug说明

接口的默认方法解决默认方法冲突java-jdk8