基于JDK8的ConcurrentHashMap中的putVal源码及注释:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
/** Implementation for put and putIfAbsent
* 实现put和putIfAbsent
* onlyIfAbsent含义:如果我们传入的key已存在我们是否去替换,true:不替换,false:替换。
* */
final V putVal(K key, V value, boolean onlyIfAbsent) {
//键值都不为空
if (key == null || value == null) throw new NullPointerException();
//发散键的hash值
int hash = spread(key.hashCode());
//桶数量 0
int binCount = 0;
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
//检查表是否初始化
if (tab == null || (n = tab.length) == 0)
//初始化表
tab = initTable();
//检查指定键所在节点是否为空
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
//通过CAS方法添加键值对
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null)))
break; // no lock when adding to empty bin 添加到空桶时没有锁
}
//如果当前节点的Hash值为MOVED
else if ((fh = f.hash) == MOVED)
//如果还在进行扩容操作就先进行扩容
tab = helpTransfer(tab, f);
else {
V oldVal = null;
//synchronized锁定此f节点
synchronized (f) {
//再次检测节点是否相同
if (tabAt(tab, i) == f) {
//如果此节点hash值大于0
if (fh >= 0) {
//桶数量为1
binCount = 1;
for (Node<K,V> e = f;; ++binCount) {
K ek;
//获取到指定的键
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
//保存老的值
oldVal = e.val;
//onlyIfAbsent:如果我们传入的key已存在我们是否去替换,true:不替换,false:替换。
if (!onlyIfAbsent)
//替换掉
e.val = value;
break;
}
Node<K,V> pred = e;
//下一个节点为空
if ((e = e.next) == null) {
//新建节点
pred.next = new Node<K,V>(hash, key,
value, null);
break;
}
}
}
//如果时树节点
else if (f instanceof TreeBin) {
Node<K,V> p;
//桶数量为2
binCount = 2;
//找到插入的位置,插入节点,平衡插入
if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
value)) != null) {
oldVal = p.val;
if (!onlyIfAbsent)
p.val = value;
}
}
}
}
//如果桶数量不为0
if (binCount != 0) {
//如果桶数量大于树化阈值
if (binCount >= TREEIFY_THRESHOLD)
//将链表转为树
treeifyBin(tab, i);
//如果老的值存在,则返回
if (oldVal != null)
return oldVal;
break;
}
}
}
//增加数量
addCount(1L, binCount);
return null;
}

这个方法的大概思路就是:

  1. 初始化操作
  2. 如果待插入键的插入位置没有节点,则通过CAS方式创建一个节点。
  3. 如果当前节点的Hash值为 static final int MOVED = -1; // hash for forwarding nodes 转发节点的哈希,则调用helpTransfer方法,如果还在进行扩容操作就先进行扩容,Helps transfer if a resize is in progress。
  4. 再有就是发生Hash碰撞时通过synchronized关键字锁定当前节点,这里有两种情况,一种是链表形式:直接遍历到尾端插入,一种是红黑树:按照红黑树结构插入。
  5. 计数

这里面有两个地方保证了putVal的并发实现,一个是没有待插入节点时的CAS技术,一个是发现有存在Hash碰撞时的synchronized关键字。

这里面涉及到“初始化”、“扩容”、“计数”的相关操作后期分析。