代码之家  ›  专栏  ›  技术社区  ›  AdamSkywalker

ConcurrentHashMap计算方法的理解代码

  •  48
  • AdamSkywalker  · 技术社区  · 7 年前

    刚刚在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\U 131

    3 回复  |  直到 7 年前
        1
  •  40
  •   Andy Turner    7 年前
    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 .

        2
  •  16
  •   Kayaman    7 年前

    虽然 r 此时是一个新变量,它被放入内部 table 立即通过 if (casTabAt(tab, i, null, r)) 这时,另一个线程可以在代码的不同部分访问它。

    内部非javadoc注释对此进行了描述

    将第一个节点插入(通过put或其变体)空箱子 只需将其装入箱子即可完成。这是目前为止 大多数键/散列分布下put操作的常见情况。 其他更新操作(插入、删除和替换)需要锁定。 我们不想浪费将不同的 用每个箱子锁定对象,因此使用箱子列表的第一个节点 本身就像一把锁。这些锁的锁定支持依赖于内置 “同步”监视器。

        3
  •  5
  •   Eugene    7 年前

    这里只有0.02美元

    你所展示的实际上只是 ReservationNode -这意味着箱子是空的 预订 某个节点的。请注意,此方法稍后将用真实节点替换此节点:

     setTabAt(tab, i, node);
    

    就我所知,这样做是为了使替换成为原子。一旦通过发布 casTabAt 如果其他线程看到它,他们就不能在上面同步,因为锁已经被持有了。

    还请注意,当bin中有一个条目时,第一个节点用于在上进行同步(在方法中位于较低的位置):

    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分钟内完成,但会抛出 Recursive Update 第9页。