JDK8代码解释中的ConcurrentHashmap
ConcurrentHashmap in JDK8 code explanation
我一直在尝试理解 JDK8 中的 ConcurrentHashMap 函数,与它在 JDK7 中的情况形成对比(除了源代码之外,一些不错的人可以很好地解释 JDK7,例如 Richard http://www.burnison.ca/articles/the-concurrency-of-concurrenthashmap)。看起来在 JDK8 中已经改变了很多 - 例如'segment' 本身已经不存在了,但是不知何故,我觉得这些更改是为了简化代码?
我在理解方法 ConcurrentHashMap.putVal(...) 时遇到了一些困难,尤其是下面的部分 - 这种直接锁定在 [=30 的头部=] 无论如何都要在 else{} 中插入列表?:
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
else {//...}
也不太确定 ConcurrentHashMap.casTabAt(...) 的代码。
另外,关于JDK8中ConcurrentHashMap.get(Object key)的源码,是不是严格意义上没有加锁(我没看到,如果有,怎么弄的没有锁工作,因为我也没有看到循环 'try-again'?)或者有某种我没有观察到的乐观锁?
如果有人能提供一些提示,我们将不胜感激。
关于putVal(K key, V value, boolean onlyIfAbsent)
方法
每个bin/bucket包含一个hash
字段,它以一种非常巧妙的方式结合了两个目的:
- 对于常规垃圾箱(大多数垃圾箱只包含一个项目),它存储此处映射键的哈希码。虽然最高位已清除(它始终设置为 0)。
- 对于特殊的 bins(目前有 3 种类型),它包含一个特殊的负值。聪明的部分是您只需要最高位来区分正值和负值,从而区分常规 bin 和特殊 bin。区分不同类型的特殊bin可以自由使用剩下的31位。
本节
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
else {//...}
是在发现映射不为空并且您尝试映射的键的容器不为空后的第一个检查。
如果您找到的垃圾箱是一种特殊类型的垃圾箱 - 转发垃圾箱,我们会很满意。转发箱是必需的,因为调整大小是同时和迭代完成的,并且已经转移(到新 table)条目仍然需要可访问(通过旧 table 中的转发箱)。
关于casTabAt((Node<K,V>[] tab, int i, Node<K,V> c, Node<K,V> v)
方法
casTabAt()
方法用于使用对象引用的比较和交换操作自动设置映射条目。您仍然可以在几乎所有使用 casTabAt()
的地方看到典型的 CAS 循环——您构造要放置的对象,然后尝试将其 CAS 到正确的位置。如果在 CAS 尝试之前进行复杂的构造感觉很奇怪,您可以看看 Jeff Preshing 的 You Can Do Any Kind of Atomic Read-Modify-Write Operation.
从某种意义上说,ConcurrentHashMap
仍然使用条带锁定,但锁定粒度更细(竞争区域现在从多 bin 段最小化到单个 bin)并且锁几乎完全被 CAS 操作取代.
关于get(Object key)
方法
get()
方法可以在没有任何锁定的情况下逃脱,因为在大多数情况下,bin 内容是使用 volatile
语义设置和检索的(通过上述 casTabAt()
方法和相关 tabAt()
方法)。如果 bin 包含映射到同一个 bin 的条目的红黑树,情况就更棘手了,你可以看到访问的 TreeBin
内的遍历总是在 synchronized
块中完成。
我一直在尝试理解 JDK8 中的 ConcurrentHashMap 函数,与它在 JDK7 中的情况形成对比(除了源代码之外,一些不错的人可以很好地解释 JDK7,例如 Richard http://www.burnison.ca/articles/the-concurrency-of-concurrenthashmap)。看起来在 JDK8 中已经改变了很多 - 例如'segment' 本身已经不存在了,但是不知何故,我觉得这些更改是为了简化代码?
我在理解方法 ConcurrentHashMap.putVal(...) 时遇到了一些困难,尤其是下面的部分 - 这种直接锁定在 [=30 的头部=] 无论如何都要在 else{} 中插入列表?:
else if ((fh = f.hash) == MOVED) tab = helpTransfer(tab, f); else {//...}
也不太确定 ConcurrentHashMap.casTabAt(...) 的代码。
另外,关于JDK8中ConcurrentHashMap.get(Object key)的源码,是不是严格意义上没有加锁(我没看到,如果有,怎么弄的没有锁工作,因为我也没有看到循环 'try-again'?)或者有某种我没有观察到的乐观锁?
如果有人能提供一些提示,我们将不胜感激。
关于putVal(K key, V value, boolean onlyIfAbsent)
方法
每个bin/bucket包含一个hash
字段,它以一种非常巧妙的方式结合了两个目的:
- 对于常规垃圾箱(大多数垃圾箱只包含一个项目),它存储此处映射键的哈希码。虽然最高位已清除(它始终设置为 0)。
- 对于特殊的 bins(目前有 3 种类型),它包含一个特殊的负值。聪明的部分是您只需要最高位来区分正值和负值,从而区分常规 bin 和特殊 bin。区分不同类型的特殊bin可以自由使用剩下的31位。
本节
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
else {//...}
是在发现映射不为空并且您尝试映射的键的容器不为空后的第一个检查。
如果您找到的垃圾箱是一种特殊类型的垃圾箱 - 转发垃圾箱,我们会很满意。转发箱是必需的,因为调整大小是同时和迭代完成的,并且已经转移(到新 table)条目仍然需要可访问(通过旧 table 中的转发箱)。
关于casTabAt((Node<K,V>[] tab, int i, Node<K,V> c, Node<K,V> v)
方法
casTabAt()
方法用于使用对象引用的比较和交换操作自动设置映射条目。您仍然可以在几乎所有使用 casTabAt()
的地方看到典型的 CAS 循环——您构造要放置的对象,然后尝试将其 CAS 到正确的位置。如果在 CAS 尝试之前进行复杂的构造感觉很奇怪,您可以看看 Jeff Preshing 的 You Can Do Any Kind of Atomic Read-Modify-Write Operation.
从某种意义上说,ConcurrentHashMap
仍然使用条带锁定,但锁定粒度更细(竞争区域现在从多 bin 段最小化到单个 bin)并且锁几乎完全被 CAS 操作取代.
关于get(Object key)
方法
get()
方法可以在没有任何锁定的情况下逃脱,因为在大多数情况下,bin 内容是使用 volatile
语义设置和检索的(通过上述 casTabAt()
方法和相关 tabAt()
方法)。如果 bin 包含映射到同一个 bin 的条目的红黑树,情况就更棘手了,你可以看到访问的 TreeBin
内的遍历总是在 synchronized
块中完成。