了解 ConcurrentHashMap 计算方法的代码
Understanding code of ConcurrentHashMap compute method
刚刚在 ConcurrentHashMap 计算方法中发现了这段奇怪的代码:(第 1847 行)
public V compute(K key,
BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
...
Node<K,V> r = new ReservationNode<K,V>();
synchronized (r) { <--- what is this?
if (casTabAt(tab, i, null, r)) {
binCount = 1;
Node<K,V> node = null;
因此,代码对仅对当前线程可用的新变量执行同步。这意味着没有其他线程可以竞争此锁或导致内存障碍效应。
这个动作的意义是什么?这是一个错误还是会导致一些我不知道的不明显的副作用?
p.s。 jdk1.8.0_131
casTabAt(tab, i, null, r)
正在发布对 r
的引用。
static final <K,V> boolean casTabAt(Node<K,V>[] tab, int i,
Node<K,V> c, Node<K,V> v) {
return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v);
}
因为c
被放入tab
,有可能被另一个线程访问,例如在 putVal
。因此,这个 synchronized
块对于排除其他线程与那个 Node
.
做其他同步事情是必要的
虽然此时 r
是一个新变量,但它会立即通过 if (casTabAt(tab, i, null, r))
放入内部 table
,此时另一个线程能够在不同部分访问它代码。
一个内部非 javadoc 评论如此描述它
Insertion (via put or its variants) of the first node in an empty bin
is performed by just CASing it to the bin. This is by far the most
common case for put operations under most key/hash distributions.
Other update operations (insert, delete, and replace) require locks.
We do not want to waste the space required to associate a distinct
lock object with each bin, so instead use the first node of a bin list
itself as a lock. Locking support for these locks relies on builtin
"synchronized" monitors.
这里只需 0.02 美元
您在那里显示的实际上只是 ReservationNode
- 这意味着 bin 是空的,并且对某个节点进行了 reservation。请注意,此方法稍后会用一个真实的节点替换此节点:
setTabAt(tab, i, node);
据我所知,这样做是为了使替换成为原子的。通过 casTabAt
发布后,如果其他线程看到它 - 它们无法同步,因为锁已经被持有。
另请注意,当 bin 中有条目时,第一个节点用于同步(在方法的下方):
boolean added = false;
synchronized (f) { // locks the bin on the first Node
if (tabAt(tab, i) == f) {
......
作为侧节点,自 8
以来,此方法已在 9
中更改。例如 运行 这个代码:
map.computeIfAbsent("KEY", s -> {
map.computeIfAbsent("KEY"), s -> {
return 2;
}
})
永远不会在 8 后完成,但会在 9 后抛出 Recursive Update
。
刚刚在 ConcurrentHashMap 计算方法中发现了这段奇怪的代码:(第 1847 行)
public V compute(K key,
BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
...
Node<K,V> r = new ReservationNode<K,V>();
synchronized (r) { <--- what is this?
if (casTabAt(tab, i, null, r)) {
binCount = 1;
Node<K,V> node = null;
因此,代码对仅对当前线程可用的新变量执行同步。这意味着没有其他线程可以竞争此锁或导致内存障碍效应。
这个动作的意义是什么?这是一个错误还是会导致一些我不知道的不明显的副作用?
p.s。 jdk1.8.0_131
casTabAt(tab, i, null, r)
正在发布对 r
的引用。
static final <K,V> boolean casTabAt(Node<K,V>[] tab, int i,
Node<K,V> c, Node<K,V> v) {
return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v);
}
因为c
被放入tab
,有可能被另一个线程访问,例如在 putVal
。因此,这个 synchronized
块对于排除其他线程与那个 Node
.
虽然此时 r
是一个新变量,但它会立即通过 if (casTabAt(tab, i, null, r))
放入内部 table
,此时另一个线程能够在不同部分访问它代码。
一个内部非 javadoc 评论如此描述它
Insertion (via put or its variants) of the first node in an empty bin is performed by just CASing it to the bin. This is by far the most common case for put operations under most key/hash distributions. Other update operations (insert, delete, and replace) require locks. We do not want to waste the space required to associate a distinct lock object with each bin, so instead use the first node of a bin list itself as a lock. Locking support for these locks relies on builtin "synchronized" monitors.
这里只需 0.02 美元
您在那里显示的实际上只是 ReservationNode
- 这意味着 bin 是空的,并且对某个节点进行了 reservation。请注意,此方法稍后会用一个真实的节点替换此节点:
setTabAt(tab, i, node);
据我所知,这样做是为了使替换成为原子的。通过 casTabAt
发布后,如果其他线程看到它 - 它们无法同步,因为锁已经被持有。
另请注意,当 bin 中有条目时,第一个节点用于同步(在方法的下方):
boolean added = false;
synchronized (f) { // locks the bin on the first Node
if (tabAt(tab, i) == f) {
......
作为侧节点,自 8
以来,此方法已在 9
中更改。例如 运行 这个代码:
map.computeIfAbsent("KEY", s -> {
map.computeIfAbsent("KEY"), s -> {
return 2;
}
})
永远不会在 8 后完成,但会在 9 后抛出 Recursive Update
。