【问题标题】:Consistency behaviour of ConcurrentHashMap.keySet().stream()ConcurrentHashMap.keySet().stream() 的一致性行为
【发布时间】:2021-07-15 11:41:14
【问题描述】:

我可以从这个带有ConcurrentHashMapmodify 方法中获得什么一致性行为?

// map is filled concurrently from multiple threads
private final ConcurrentHashMap<String, Object> map = new ConcurrentHashMap<>();


public void modify(Object newValue) {
  map.keySet().stream()
    .filter(/* some filter */)
    .forEach(k -> {
      if (/* k has some property */) {
        map.put(k, newValue);
      } else {
        map.remove(k);
      }
    });
}

我发现通过阅读 Javadocs 很难得到明确的答案,keySet()stream() 和流内映射条目的修改如何相互作用。我知道keySet()spliteratorweakly consistent,所以流至少应该遍历所有元素,因为它们在过去的某个时间点只存在一次。并发集合支持在流执行期间修改数据源(请参阅Non-interference)。我总是只修改当前键而不接触foreach lambda 中的其他映射条目,这可能是相关的。

那么上面的代码是不是以一种弱一致的方式“都很好”,还是有一些我应该注意的警告?


注意: 上面的代码也没有真正的意义,但这是简化的代码,省略了其他与并发相关的方面。

使用不同的方法可以更好地编写代码,例如forEach 在地图上。如果它增加了答案,请随时提出改进建议,但请仅提供回答我实际问题的答案。我不是在问如何让它变得更好,我只是想知道在我的示例中所有部分是如何交互的。

【问题讨论】:

  • 是否可以假设在Stream操作之前已经完成了“多线程并发填充map”步骤,并且在Stream操作期间没有其他并发修改?或者一般来说,不需要 Stream 操作进行更新的原子性?
  • @Holger 不,实际上可以在“修改”方法的同时任意修改地图。原子性在最低层次上是必需的(不要破坏数据结构),但在更高层次上不需要。让我在具体的例子中阐明我的要求:a)我在“修改”中未触及的条目的任何修改不应干扰我的方法或其他修改的结果; b) 对我正在触及的条目的任何修改都不应破坏“修改”流、地图或单个条目,但我不期望有一个合理的“全局真相”结果。
  • 我在代码中添加了过滤流操作只是为了让它更真实一点,所以我上面的评论也更有意义。但是如果没有过滤器,问题仍然是完全相同的,我认为从并发的角度来看它没有什么不同。
  • 迭代和修改逻辑没有问题,但是当有并发修改时,您必须注意,当您执行if (/* k has some property */)的检查时,其他一些线程可能会删除映射map.put(k, newValue); 会重新添加它。同样,当您执行map.remove(k); 并且随后不同的线程添加相同的键时,不清楚您是否可以在相同的迭代中再次遇到该键。

标签: java concurrency java.util.concurrent concurrenthashmap concurrentmodification


【解决方案1】:

流应该至少遍历所有元素,因为它们在过去的某个时间点只存在一次

我会从短语中删除“全部”,因为这种措辞可能会给人一种错误的印象,即流会遍历整个 ConcurrentHashMap 的一些原子快照。
实际上,流可能反映了地图的(一些)修改,这些修改发生在流创建之后。因此,流发出的第一个键和最后一个键可能从未同时出现在映射中。

wording for weakly consistent(由the keySet()'s javadoc 引用)提到:

他们[即Iterators 和 Spliterators] 保证遍历元素,因为它们在构造时就存在一次,并且可能(但不保证)反映构造之后的任何修改。

ConcurrentHashMap中的Traverser(迭代器和拆分器的基类)也称为in the javadoc

封装了对 containsValue 等方法的遍历;也可作为其他迭代器和拆分器的基类。

方法提前访问一次在迭代器构造时可到达的每个仍然有效的节点。它可能会丢失一些在访问 bin 后添加到 bin 中的内容,这可以保证一致性。

“过去的某个时间点”也很模糊:您看不到在创建 stream() 之前从地图中删除的键。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-12-29
    • 2020-05-14
    • 2011-03-23
    • 1970-01-01
    • 1970-01-01
    • 2020-10-19
    • 2015-09-17
    • 1970-01-01
    相关资源
    最近更新 更多