Java 8+ ConcurrentHashMap 锁条带化
Java 8+ ConcurrentHashMap lock striping
我一直在通读 Brian Goetz 的 Concurency in Practice
。
在关于 Lock Striping 的章节中写到 ConcurrentHashMap
使用 16 个桶来改进多线程的多线程访问:
Lock splitting can sometimes be extended to partition locking on a variablesized set of independent objects, in which case it is called lock striping. For example, the implementation of ConcurrentHashMap uses an array of 16 locks, each of which guards 1/16 of the hash buckets; bucket N is guarded by lock N mod 16.
我已经阅读了那些问题:
ConcurrentHashMap locking
Need simple explanation how “lock striping” works with ConcurrentHashMap
但是这些答案对 Java 版本 <= 7 有效。
对于 Java 8+,行为似乎发生了重大变化。对于 Java 8+,似乎不是针对段而是针对 table (transient volatile ConcurrentHashMap.Node<K, V>[] table;
) 中的特定节点获取锁。例如对于 putVal
操作:
ConcurrentHashMap.Node var7;
.... ///retrive node for var7
synchronized(var7) {
....
}
还有来自Java8 + 字段像DEFAULT_CONCURRENCY_LEVEL
和class Segment
似乎在实现中没有使用(它只在私有方法中使用writeObject::ObjectOutputStream
并且此方法未在 ConcurrentHashMap
实现中的任何地方调用。
ConcurrentHashMap
实施发生如此重大变化的原因是什么?
如果 class Segment
未使用并且 DEFAULT_CONCURRENCY_LEVEL
之类的字段也未使用 - 为什么不从实现中删除它 - 是否用于某些历史原因?
如果我们不锁定段,就像以前 Java 版本 < 7 那样,仅锁定特定节点是否足够?如果是 - 为什么?这是否意味着我们在这里不需要锁条带化?
What is the cause of such significant change in ConcurrentHashMap implementation?
减少内存占用(原因之一)。
We do not want to waste the space required to associate a distinct lock object with each bin, so instead use the first node of a bin list itself as a lock. Locking support for these locks relies on builtin "synchronized" monitors.
openjdk > jdk8 > java.util.concurrent.ConcurrentHashMap.java
> Line 314
If class Segment
is unused and also field like DEFAULT_CONCURRENCY_LEVEL
is also unused - why not get rid of it from implementation - is it for some historical reasons?
确保序列化兼容性。
We also declare an unused Segment
class that is instantiated in minimal form only when serializing.
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
If we are not locking on segments, like it used to be for Java version < 7, is locking only on specific node sufficient?
没有
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. Because new nodes are always appended to lists, once a node is first in a bin, it remains first until deleted or the bin becomes invalidated (upon resizing).
openjdk > jdk8 > java.util.concurrent.ConcurrentHashMap.java
> Line 320
What is the cause of such significant change in ConcurrentHashMap
implementation?
* 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
class 由于兼容性原因未使用,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.
... is locking only on specific node sufficient? If yes - why?
* 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.
我一直在通读 Brian Goetz 的 Concurency in Practice
。
在关于 Lock Striping 的章节中写到 ConcurrentHashMap
使用 16 个桶来改进多线程的多线程访问:
Lock splitting can sometimes be extended to partition locking on a variablesized set of independent objects, in which case it is called lock striping. For example, the implementation of ConcurrentHashMap uses an array of 16 locks, each of which guards 1/16 of the hash buckets; bucket N is guarded by lock N mod 16.
我已经阅读了那些问题:
ConcurrentHashMap locking
Need simple explanation how “lock striping” works with ConcurrentHashMap
但是这些答案对 Java 版本 <= 7 有效。
对于 Java 8+,行为似乎发生了重大变化。对于 Java 8+,似乎不是针对段而是针对 table (transient volatile ConcurrentHashMap.Node<K, V>[] table;
) 中的特定节点获取锁。例如对于 putVal
操作:
ConcurrentHashMap.Node var7;
.... ///retrive node for var7
synchronized(var7) {
....
}
还有来自Java8 + 字段像DEFAULT_CONCURRENCY_LEVEL
和class Segment
似乎在实现中没有使用(它只在私有方法中使用writeObject::ObjectOutputStream
并且此方法未在 ConcurrentHashMap
实现中的任何地方调用。
ConcurrentHashMap
实施发生如此重大变化的原因是什么?如果 class
Segment
未使用并且DEFAULT_CONCURRENCY_LEVEL
之类的字段也未使用 - 为什么不从实现中删除它 - 是否用于某些历史原因?如果我们不锁定段,就像以前 Java 版本 < 7 那样,仅锁定特定节点是否足够?如果是 - 为什么?这是否意味着我们在这里不需要锁条带化?
What is the cause of such significant change in ConcurrentHashMap implementation?
减少内存占用(原因之一)。
We do not want to waste the space required to associate a distinct lock object with each bin, so instead use the first node of a bin list itself as a lock. Locking support for these locks relies on builtin "synchronized" monitors.
openjdk > jdk8 >
java.util.concurrent.ConcurrentHashMap.java
> Line 314
If class
Segment
is unused and also field likeDEFAULT_CONCURRENCY_LEVEL
is also unused - why not get rid of it from implementation - is it for some historical reasons?
确保序列化兼容性。
We also declare an unused
Segment
class that is instantiated in minimal form only when serializing.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
If we are not locking on segments, like it used to be for Java version < 7, is locking only on specific node sufficient?
没有
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. Because new nodes are always appended to lists, once a node is first in a bin, it remains first until deleted or the bin becomes invalidated (upon resizing).
openjdk > jdk8 >
java.util.concurrent.ConcurrentHashMap.java
> Line 320
What is the cause of such significant change in
ConcurrentHashMap
implementation?
* 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
class 由于兼容性原因未使用,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.
... is locking only on specific node sufficient? If yes - why?
* 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.