了解ConcurrentHashMap计算方法的代码

时间:2017-12-11 12:16:09

标签: java multithreading concurrenthashmap

刚刚在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

3 个答案:

答案 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