【发布时间】:2021-07-15 11:41:14
【问题描述】:
我可以从这个带有ConcurrentHashMap 的modify 方法中获得什么一致性行为?
// 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() 的spliterator 是weakly 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