【问题标题】:Understanding code of ConcurrentHashMap compute method理解 ConcurrentHashMap 计算方法的代码
【发布时间】:2018-05-24 23:14:07
【问题描述】:

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

因此代码对仅对当前线程可用的新变量执行同步。这意味着没有其他线程可以竞争这个锁或造成内存屏障效应。

这个动作有什么意义?这是一个错误还是会导致一些我不知道的不明显副作用?

附言jdk1.8.0_131

【问题讨论】:

  • 第 740 行周围有一个注释块,它可能会解释为什么要这样做。据我了解,该节点通过Unsafe 操作与tab 变量交换,因此此时所有线程都可能开始看到它,并且此时它应该被创建节点的线程锁定,因此其他线程必须等待对节点执行任何操作。否则,有可能在交换发生后立即参加比赛。

标签: java multithreading concurrenthashmap


【解决方案1】:
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 执行其他同步操作是必要的。

【讨论】:

  • 不确定“泄漏”是正确的动词。 “泄漏”通常意味着意外,但代码使节点对其他线程可见并非偶然。
  • “曝光”在这里可能是更好的选择
  • @Caleth 我已经在考虑将其更改为“发布”,如“不安全的发布”(尽管在这种情况下它不是“不安全”;但这只是一个众所周知的引用术语在当前执行线程之外可用)。
【解决方案2】:

这里只需 0.02 美元

您在此处显示的内容实际上只是ReservationNode - 这意味着 bin 是空的,并且 保留 某些节点。请注意,此方法稍后会将此节点替换为真实节点:

 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

【讨论】:

    【解决方案3】:

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

    一个内部的非 javadoc 注释如此描述它

    在空 bin 中插入(通过 put 或其变体)第一个节点 只需将其放入垃圾箱即可执行。这是迄今为止最 大多数键/哈希分布下的 put 操作的常见情况。 其他更新操作(插入、删除和替换)需要锁定。 我们不想浪费关联不同的空间所需的空间 用每个 bin 锁定对象,所以改为使用 bin 列表的第一个节点 本身就是一把锁。对这些锁的锁定支持依赖于内置 “同步”监视器。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-10-01
      • 1970-01-01
      • 1970-01-01
      • 2021-03-06
      • 1970-01-01
      • 2016-02-28
      • 1970-01-01
      • 2015-03-05
      相关资源
      最近更新 更多