我是靠谱客的博主 俏皮小笼包,这篇文章主要介绍ConcurrentHashMap_get与remove方法源码分析ConcurrentHashMap_get与remove方法源码分析,现在分享给大家,希望可以做个参考。

ConcurrentHashMap_get与remove方法源码分析

文章目录

  • ConcurrentHashMap_get与remove方法源码分析
    • 1. get() 方法
    • 1.1 ForwardingNode的find() 方法
    • 2. remove() 方法

1. get() 方法

与普通的Map一样,通过key查找元素。

在ConcurrentHashMap的get方法中也没有任何加锁逻辑。

复制代码
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
/** * 返回指定键映射到的值,如果该映射不包含键的映射,则返回null。 * 更正式地说,如果这个映射包含从键k到值v的映射,使得key.equals(k), * 那么这个方法返回v;否则将返回null。(最多只能有一个这样的映射。) * @param key 要返回其关联值的键 * @return */ public V get(Object key) { // tab 引用table数组 // e 当前元素 // p 目标元素 // eh 当前元素hash // ek 当前元素key Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek; // 获得扰动运算后的hash值 int h = spread(key.hashCode()); // 条件1:table不为 null // 条件2:长度大于0 // 条件3:桶位上的元素不为 null if ((tab = table) != null && (n = tab.length) > 0 && (e = tabAt(tab, (n - 1) & h)) != null) { /* 对比与头结点的key是否完全一致 1.比较key的hash值是否一致 2.比较key的内存地址是否一致 或 进行equals的返回true 同时满足这两个条件才能认为key完全一致 */ if ((eh = e.hash) == h) { if ((ek = e.key) == key || (ek != null && key.equals(ek))) // 直接在桶位上找到了对应元素,直接返回value值 return e.val; } /* hash值小于0,有两种情况 1. hash = -1,表示当前桶位是FWD节点,表示当前桶位中的节点已经被迁移了 2. hash = -2, 表示当前桶位是TreeBin节点, 如果是 FWD 节点的话,会进入对应的 FWD 中定义的 find 方法进行查找 如果是红黑树,则会进入TreeNode内部的find方法进行查找(fwd节点和treeBin都继承了Node节点并重写了find方法) */ else if (eh < 0) return (p = e.find(h, key)) != null ? p.val : null; // 桶位上的是链表,同时头节点不是要查找的元素 // 开始遍历链表,查找元素 while ((e = e.next) != null) { if (e.hash == h && ((ek = e.key) == key || (ek != null && key.equals(ek)))) return e.val; } } return null; }

流程总结:

  1. 计算key的hash值
  2. 根据key的hash计算桶位位置,判断桶位上是否有元素
  3. 当前桶位如果是fwd节点或者是红黑树节点,则调用各自的find()方法。
  4. 当前桶位不是fwd节点也不是红黑树节点,则遍历桶位,进行查找。

get方法中调用了FWD和TreeBin中的find方法。

下面对find方法进行分析。

1.1 ForwardingNode的find() 方法

同样没有加锁逻辑。

复制代码
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
Node<K,V> find(int h, Object k) { // 循环以避免转发节点上的任意深度递归 // tab: 新建散列表的引用 outer: for (Node<K,V>[] tab = nextTable;;) { //------------------外轮自旋开始-------------------- // 新散列表上元素的引用 // 新散列表长度 Node<K,V> e; int n; // 条件1:永远不成立 // 条件2:永远不成立 // 条件3:永远不成立 // 条件4:在新扩容表中重新路由定位 hash 对应的头结点 // true -> 1.在 oldTable 中对应的桶位在迁移之前就是 null // false -> 2.扩容完成后,有其它写线程,将此桶位设置为 null if (k == null || tab == null || (n = tab.length) == 0 || (e = tabAt(tab, (n - 1) & h)) == null) return null; //------------------内轮自旋开始-------------------- // 前置条件:扩容后的表对应hash的桶位一定不是null,e为此桶位的头结点 // e可能表示的类型: // 1.node 类型 // 2.TreeBin 类型 // 3.FWD 类型 for (;;) { // eh 新扩容后表指定桶位的当前节点的hash // ek 新扩容后表指定桶位的当前节点的key int eh; K ek; // 如果成立:表示出入的key命中新扩容的表中的数据 if ((eh = e.hash) == h && ((ek = e.key) == k || (ek != null && k.equals(ek)))) // 返回命中元素 return e; // eh < 0 有两种情况 // 1. 表示TreeBin类型 // 2. FWD类型(新扩容的表,在并发很大的情况下再次发生了扩容,所以可能在此方法再次拿到FWD类型) if (eh < 0) { // 判断是否是FWD结点 if (e instanceof ForwardingNode) { tab = ((ForwardingNode<K,V>)e).nextTable; // 调到外轮自旋 continue outer; } else // 说明此桶位为 TreeBin 节点,使用TreeBin.find 查找红黑树中相应节点。 return e.find(h, k); } // 前置条件:当前桶位头结点 并没有命中查询,说明此桶位是链表 // 1. 将当前元素指向链表的下一个元素 // 2. 判断当前元素的下一个位置是否为空 // true -> 说明迭代到链表末尾,未找到对应的数据,返回Null if ((e = e.next) == null) return null; } //------------------内轮自旋结束-------------------- } //------------------外轮自旋结束-------------------- } }

2. remove() 方法

复制代码
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
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
public V remove(Object key) { //调用了替换节点的方法 return replaceNode(key, null, null); } || || / /* * @param Object key -> 表示要替换的key * @param V value -> 要替换的目标值 * @param Object cv(compare value) -> 如果cv不为NULL,则需要既对比key还要对比 * cv,这两个参数都一致,才能替换目标值 */ final V replaceNode(Object key, V value, Object cv) { //计算key的hash int hash = spread(key.hashCode()); //引用哈希表 Node<K,V>[] tab = table; //自旋 for (;;) { /* * f表示桶位头节点 * n 表示table长度 * i表示hash寻址后命中的下标 * fh表示桶位头节点的hash值 */ Node<K,V> f; int n, i, fh; /* * CASE1 * 如果table为NULL,或者当前寻址后命中的桶位没有元素, * 直接break循环。 */ if (tab == null || (n = tab.length) == 0 || (f = tabAt(tab, i = (n - 1) & hash)) == null) break; /* * CASE2 * 当前桶位是fwd节点,说明当前table正在扩容中, * 由于当前是写操作,需要尝试帮助table扩容。 */ else if ((fh = f.hash) == MOVED) tab = helpTransfer(tab, f); /* * CASE3 * 当前桶位是链表或者红黑树 */ else { //保留替换之前的value V oldVal = null; //校验标记 用于判断是否锁错对象,从而进入下一次的自旋。 boolean validated = false; //锁定当前桶位的头结点(分段锁思想) synchronized (f) { /* * 判断锁定的对象是否是当前桶位头节点, * 防止当前线程加锁之前,其他线程修改过桶位的头节点。 */ if (tabAt(tab, i) == f) { //哈希值大于0,说明桶位为正常的Node节点(或者Node链表) if (fh >= 0) { //validated赋值为true。 validated = true; /* * e 表示当前循环的元素 * pred 表示上一个元素 */ Node<K,V> e = f, pred = null; for (;;) { //当前桶位的key。 K ek; //找到了指定key if (e.hash == hash && ((ek = e.key) == key || (ek != null && key.equals(ek)))) { //将找到的指定节点的val赋值给ev V ev = e.val; /* * cv == null -> true 表示这是一个删除操作 * * cv == ev -> true 表示这是一个替换操作 */ if (cv == null || cv == ev || (ev != null && cv.equals(ev))) { oldVal = ev; //直接将value替换 if (value != null) e.val = value; /* * 当前节点不是头结点,并且这是一次删除操作 * 直接在链表中将e删除即可 */ else if (pred != null) pred.next = e.next; /* * e是头结点,调用setTab方法将e.next设置为 * 头结点即可,变向的将e删除。 */ else setTabAt(tab, i, e.next); } break; } pred = e; //没有找到指定节点,退出。 if ((e = e.next) == null) break; } } //红黑树 else if (f instanceof TreeBin) { //修改标志位 validated = true; //转换为指定的TreeBin节点 TreeBin<K,V> t = (TreeBin<K,V>)f; /* * r 表示红黑树根节点 * p 表示红黑树中查找到的对应的key一致的Node。 */ TreeNode<K,V> r, p; if ((r = t.root) != null && //在红黑树中找到了指定的节点 (p = r.findTreeNode(hash, key, null)) != null) { V pv = p.val; if (cv == null || cv == pv || (pv != null && cv.equals(pv))) { //原值赋给oldVal。 oldVal = pv; //value不为NULL,替换 if (value != null) p.val = value; //删除 else if (t.removeTreeNode(p)) /* * 如果删除后树的元素个数较少则退化成链表 * t.removeTreeNode(p)这个方法返回true表示删 * 除节点后树的元素个数是否较少(boolean) */ setTabAt(tab, i, untreeify(t.first)); } } } } } /* * 当其他线程修改过桶位的头节点时,说明当前线程锁错了对象 * 此时validated为false,不会进入到下面的if的逻辑中终止,则会进入下次自旋 * * 如果此时validated为true,说明进入过真正的删除逻辑中,然后判断是否真正 * 的删除了节点,是的话则调用addCount()将元素数量减一。然后结束方法。 */ if (validated) { //oldVal不为NULL,说明操作成功。 if (oldVal != null) { //value == null 说明这是一次删除操作,更新节点个数 if (value == null) addCount(-1L, -1); //返回旧值 return oldVal; } break; } } } return null; }

方法流程总结:

  1. remove底层调用的是replaceNode方法,传入的后两个参数都是NULL,表示当前的替换操作是删除。
  2. 计算hash值,自旋去尝试删除
  3. 寻址后桶位上没有元素,返回NULL。
  4. 桶位上是链表,则遍历链表查找元素进行删除.
  5. 桶位上是fwd节点,表示正在扩容,先尝试帮助扩容,然后继续循环尝试删除
  6. 桶位上形成了红黑树,遍历树查找元素,然后删除,删除后树中元素节点数较少,则退化为链表
  7. 如果确实删除了元素 (非value替换),调用addCount()将元素个数-1,
  8. 删除过程加锁(sycnhronized)锁定当前桶位的头结点。

最后

以上就是俏皮小笼包最近收集整理的关于ConcurrentHashMap_get与remove方法源码分析ConcurrentHashMap_get与remove方法源码分析的全部内容,更多相关ConcurrentHashMap_get与remove方法源码分析ConcurrentHashMap_get与remove方法源码分析内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(61)

评论列表共有 0 条评论

立即
投稿
返回
顶部