JDK8中的ConcurrentHashmap代码说明

时间:2016-04-12 02:33:45

标签: concurrency java-8 concurrenthashmap

我一直在尝试理解JDK8中的ConcurrentHashMap函数,与JDK7中的它是如何形成对比的(除了源代码之外,还可以找到一些很好的人解释,例如Richard {{ 3}})。它看起来在JDK8中已经发生了很大变化 - 例如没有更多的细分'本身,但不知怎的,我觉得这些变化是为了让代码更简单?

  1. 我很难理解方法ConcurrentHashMap.putVal(...),尤其是以下部分 - 这是直接锁定在'段&#39的头部;无论如何列表插入else {}?:

        else if ((fh = f.hash) == MOVED)
            tab = helpTransfer(tab, f);
        else {//...}
    
  2. 不太确定ConcurrentHashMap.casTabAt(...)的代码。

  3. 另外,关于JDK8中ConcurrentHashMap.get(Object key)的源代码,它是否完全没有锁定(我没有看到任何内容,如果是这样,它如何在没有锁定的情况下工作我没有看到一个循环“再试一次”?或者还有一些我没有观察到的乐观锁定?

  4. 感谢是否有人可以提供一些提示。

1 个答案:

答案 0 :(得分:6)

关于putVal(K key, V value, boolean onlyIfAbsent)方法

每个bin / bucket包含一个hash字段,它以非常聪明的方式结合了两个目的:

  • 对于常规分档(大多数分箱只包含单个项目),它会存储映射此键的哈希码。最高位被清除(它总是设置为0)。
  • 对于特殊箱(目前有3种类型),它包含一个特殊的负值。聪明的部分是你只需要顶部位来区分正值和负值,因此可以从特殊箱中区分常规箱。区分不同类型的特殊箱可以自由使用剩余的31位。

本节

else if ((fh = f.hash) == MOVED)
    tab = helpTransfer(tab, f);
else {//...}

是在发现地图不为空并且您尝试映射的密钥的bin不为空之后的第一次检查。

如果您找到的垃圾箱是特殊类型的垃圾箱 - 转发箱,则会感到满意。转发箱是必需的,因为调整大小是同时并且迭代完成并且已经转移(到新表),条目仍然需要可访问(通过旧表中的转发箱)。

关于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仍然使用条带锁定,但具有更精细的锁粒度(竞争区域现在从多仓区段最小化到单个区间)并且锁几乎完全被CAS操作取代。 / p>

关于get(Object key)方法

get()方法可以在没有任何锁定的情况下离开,因为在大多数情况下,使用volatile语义(通过前面提到的casTabAt()方法和相关的{{}}设置和检索bin内容{1}}方法)。如果bin包含映射到同一bin的红黑树条目,则情况比较棘手,您可以看到访问tabAt()内的遍历始终在TreeBin块中完成。