【问题标题】:ConcurrentHashmap in JDK8 code explanationJDK8代码解释中的ConcurrentHashmap
【发布时间】:2016-04-12 02:33:44
【问题描述】:

我一直试图理解 JDK8 中的 ConcurrentHashMap 函数,这与 JDK7 中的情况形成对比(除了源代码之外,还可以找到一些很好的人很好地解释了这些函数,例如 Richard @987654321 @)。看起来在 JDK8 中已经发生了相当大的变化 - 例如本身没有更多的“段”,但不知何故我觉得这些变化是为了让代码更简单?

  1. 我在理解 ConcurrentHashMap.putVal(...) 方法时遇到了一些困难,尤其是以下部分 - 这种直接锁定在“段”列表的头部是否仍然可以插入到 else{} ?:

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

  3. 1234563 '也没有看到循环'重试'?)或者有某种我没有观察到的乐观锁?

如果有人可以提供一些提示,不胜感激。

【问题讨论】:

    标签: concurrency java-8 concurrenthashmap


    【解决方案1】:

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

    每个 bin/bucket 都包含一个 hash 字段,它以非常巧妙的方式结合了两个用途:

    • 对于常规箱(大多数箱只包含一个项目),它存储映射的此处键的哈希码。但最高位被清除(它始终设置为 0)。
    • 对于特殊的 bin(目前有 3 种类型),它包含一个特殊的负值。聪明的部分是你只需要最高位来区分正值和负值,因此需要区分常规 bin 和特殊 bin。区分不同类型的特殊 bin 可以随意使用剩余的 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 仍然使用条带锁定,但具有更精细的锁定粒度(竞争区域现在从多 bin 段最小化到单个 bin)并且锁定几乎完全被 CAS 操作取代。

    关于get(Object key) 方法

    get() 方法可以在没有任何锁定的情况下逃脱,因为在大多数情况下,bin 内容是使用volatile 语义设置和检索的(通过上述casTabAt() 方法和相关的tabAt() 方法)。如果 bin 包含映射到同一 bin 的条目的红黑树,则情况会更加棘手,并且您可以看到访问的 TreeBin 内的遍历始终在 synchronized 块中完成。

    【讨论】:

      猜你喜欢
      • 2014-10-01
      • 2018-05-24
      • 2020-03-26
      • 2016-08-02
      • 1970-01-01
      • 1970-01-01
      • 2015-03-23
      • 2012-07-20
      相关资源
      最近更新 更多