刚刚在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
答案 0 :(得分:39)
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
,所以有可能被另一个线程访问,例如在https://jgofficial1.000webhostapp.com/index.html。因此,必须使用此synchronized
块来排除其他线程与Node
执行其他同步事务。
答案 1 :(得分:16)
虽然此时r
是一个新变量,但它会立即通过table
放入内部if (casTabAt(tab, i, null, r))
,此时另一个线程可以在其中的不同部分访问它代码。
内部非javadoc评论因此描述
将第一个节点插入(通过put或其变体)在空箱中 只需将其CAS设置到bin中即可执行。这是迄今为止最多的 大多数键/哈希分布下的put操作的常见情况。 其他更新操作(插入,删除和替换)需要锁定。 我们不想浪费相关空间所需的空间 锁定每个bin的对象,所以改为使用bin列表的第一个节点 本身就像一把锁。锁定对这些锁的支持依赖于内置 “同步”监视器。
答案 2 :(得分:3)
这里只需0.02美元
你所展示的实际上只有ReservationNode
- 意味着bin是空的,并且某个Node的预订。请注意,此方法稍后将使用实际的节点替换此节点:
setTabAt(tab, i, node);
所以这样做是为了让替换成为原子,据我所知。一旦通过casTabAt
发布,如果其他线程看到它 - 由于锁定已经存在,它们无法在其上同步。
另请注意,当bin中有Entry时,第一个Node用于同步(在方法中更下方):
boolean added = false;
synchronized (f) { // locks the bin on the first Node
if (tabAt(tab, i) == f) {
......
与副节点一样,此方法在9
中已更改,因为8
。例如,运行此代码:
map.computeIfAbsent("KEY", s -> {
map.computeIfAbsent("KEY"), s -> {
return 2;
}
})
永远不会在8完成,但会在9中抛出Recursive Update
。