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 }
参考资料