【问题标题】:Java 8+ ConcurrentHashMap lock stripingJava 8+ ConcurrentHashMap 锁条带化
【发布时间】:2019-12-05 22:37:25
【问题描述】:

我一直在阅读 Brian Goetz 的 Concurency in Practice。

在Lock Striping一章中写到ConcurrentHashMap使用16个桶来改善多线程的多线程访问:

锁分割有时可以扩展到对可变大小的独立对象集进行分区锁定,在这种情况下,它被称为锁条带化。例如,ConcurrentHashMap 的实现使用了一个 16 个锁的数组,每个锁守卫 1/16 的哈希桶; bucket N 由 lock N mod 16 保护。

我已经阅读了这些问题:

ConcurrentHashMap locking

Need simple explanation how “lock striping” works with ConcurrentHashMap

但是,这些答案对 Java 版本

对于 Java 8+,行为似乎发生了显着变化。对于 Java 8+,似乎不是为段获取锁,而是为表中的特定节点获取锁 (transient volatile ConcurrentHashMap.Node<K, V>[] table;)。例如putVal 操作:

ConcurrentHashMap.Node var7;

.... ///retrive node for var7

synchronized(var7) {
....
}

还有来自 Java8 + 字段的 DEFAULT_CONCURRENCY_LEVEL 和类 Segment 似乎在实现中未使用(它仅在私有方法 writeObject::ObjectOutputStream 中使用,并且在 ConcurrentHashMap 实现中的任何地方都不会调用此方法)。

  1. ConcurrentHashMap 实施发生如此重大变化的原因是什么?

  2. 如果类 Segment 未使用,并且像 DEFAULT_CONCURRENCY_LEVEL 这样的字段也未使用 - 为什么不从实现中删除它 - 是出于某些历史原因吗?

  3. 如果我们不锁定段,就像以前用于 Java 版本

【问题讨论】:

标签: java multithreading concurrenthashmap


【解决方案1】:

ConcurrentHashMap 发生如此显着变化的原因是什么 实施?

ConcurrentHashMap.java#l272:

* The primary design goal of this hash table is to maintain
* concurrent readability (typically method get(), but also
* iterators and related methods) while minimizing update
* contention.

Segment 类由于兼容性原因仍未使用,ConcurrentHashMap.java#l481:

* Maintaining API and serialization compatibility with previous
* versions of this class introduces several oddities. Mainly:
* [...]
* We also declare an unused "Segment" class that is
* instantiated in minimal form only when serializing.

... 仅锁定特定节点就足够了吗?如果是 - 为什么?

ConcurrentHashMap.java#l320:

* Using the first node of a list as a lock does not by itself
* suffice though: When a node is locked, any update must first
* validate that it is still the first node after locking it,
* and retry if not.

【讨论】:

    【解决方案2】:

    ConcurrentHashMap 实现发生如此重大变化的原因是什么?

    为了减少内存占用(原因之一)。

    我们不想浪费将不同的锁对象与每个 bin 关联所需的空间,因此将 bin 列表的第一个节点本身用作锁。对这些锁的锁定支持依赖于内置的“同步”监视器。

    openjdk > jdk8 > java.util.concurrent.ConcurrentHashMap.java > Line 314


    如果 Segment 类未使用,并且 DEFAULT_CONCURRENCY_LEVEL 之类的字段也未使用 - 为什么不从实现中删除它 - 是否出于某些历史原因?

    为了确保序列化兼容性。

    我们还声明了一个未使用的 Segment 类,该类仅在序列化时以最小形式实例化。

    openjdk > jdk8 > java.util.concurrent.ConcurrentHashMap.java > Line 486

    /**
     * The default concurrency level for this table. Unused but
     * defined for compatibility with previous versions of this class.
     */
    private static final int DEFAULT_CONCURRENCY_LEVEL = 16;
    

    openjdk > jdk8 > java.util.concurrent.ConcurrentHashMap.java > Line 526

    /**
     * Stripped-down version of helper class used in previous version,
     * declared for the sake of serialization compatibility
     */
    static class Segment<K,V> extends ReentrantLock implements Serializable {
        private static final long serialVersionUID = 2249069246763182397L;
        final float loadFactor;
        Segment(float lf) { this.loadFactor = lf; }
    }
    

    openjdk > jdk8 > java.util.concurrent.ConcurrentHashMap.java > Line 1366


    如果我们不锁定段,就像过去对 Java 版本

    没有。

    使用列表的第一个节点作为锁本身是不够的:当一个节点被锁定时,任何更新都必须首先验证它在锁定它之后仍然是第一个节点,并且如果没有,请重试。因为新节点总是附加到列表中,所以一旦一个节点首先出现在 bin 中,它就会一直保持在第一位,直到被删除或 bin 失效(在调整大小时)。

    openjdk > jdk8 > java.util.concurrent.ConcurrentHashMap.java > Line 320

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-04-15
      • 1970-01-01
      • 2017-10-22
      • 1970-01-01
      • 2021-12-14
      • 1970-01-01
      • 1970-01-01
      • 2015-12-17
      相关资源
      最近更新 更多