Java 8+ ConcurrentHashMap 锁条带化

mic*_*alk 5 java multithreading concurrenthashmap

我一直在通读Concurency in PracticeBrian Goetz 的著作。

在关于 Lock Striping 的章节中写到ConcurrentHashMap使用 16 个存储桶来改进多个线程的多线程访问:

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

我已经阅读了这些问题:

ConcurrentHashMap 锁定

需要简单解释“锁条带化”如何与 ConcurrentHashMap 配合使用

但是,这些答案对 Java 版本 <= 7 有效。

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

ConcurrentHashMap.Node var7;

.... ///retrive node for var7

synchronized(var7) {
....
}
Run Code Online (Sandbox Code Playgroud)

并且从 Java8 + 字段 likeDEFAULT_CONCURRENCY_LEVEL和 classSegment似乎在实现中未使用(它仅用于私有方法中,writeObject::ObjectOutputStream并且在ConcurrentHashMap实现中的任何地方都不会调用此方法)。

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

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

  3. 如果我们不锁定段,就像 Java 版本 <7 以前那样,仅锁定特定节点就足够了吗?如果是 - 为什么?这是否意味着我们不需要在这里进行锁条带化?

Ole*_*hov 3

实施过程中出现如此重大变化的原因是什么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.
Run Code Online (Sandbox Code Playgroud)

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.
Run Code Online (Sandbox Code Playgroud)

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

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.
Run Code Online (Sandbox Code Playgroud)