在ConcurrentHashMap上使用forEach时,会反映更新还是其行为与故障安全迭代器相似?

问题描述:

如果我在ConcurrentHashMap上启动forEach操作,并且其他线程仍在执行放入此映射,我会看到其他bin的新更新吗?在ConcurrentHashMap上使用forEach时,会反映更新还是其行为与故障安全迭代器相似?

原因是我试图找到最有效的方法来向侦听器广播ConcurrentHashMap的内容,而不会引起新的数据写入者对地图的争用。但是当我通知听众时,我希望所有听众都能收到地图的相同快照。

+4

90%肯定它会和迭代器一样保证:[_weak consistency_](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/package-summary。 HTML#弱)。 –

It's not fail safe,但更新不反映你认为他们的方式;所以如果你已经看到一个垃圾箱之前,你不会再从该垃圾箱获得更新。

如果你知道你可以spread作品内部甚至导致OOM(这是从我从霍尔格回答一个问题一个很好的评论,但我似乎无法立刻找到它......)

ConcurrentHashMap<Integer, Integer> chm = new ConcurrentHashMap<>(500_000_000); 
chm.put(1, 1); 
chm.forEach((key, value) -> chm.put(++value^(value>>>16), value)); 
+0

这只能运行,直到容量增加,所以你需要足够的初始容量让它运行到OOME。顺便说一句,你有一个错字'cMap'→'chm'。 – Holger

+0

@霍尔权利......完全是我的错 – Eugene

The class-level API docs有这样一段话:

检索操作(包括get)一般不会阻塞,因此,可能与更新操作(包括putremove)重叠。检索反映了最近完成的更新操作的结果。 (更正式地说,对于给定键的更新操作承载与该键的任何(非空)检索报告更新值的发生之前关系。)对于诸如putAllclear之类的集合操作,并发检索可以反映插入或删除只有一些条目。 类似地,Iterators,SpliteratorsEnumerations返回反映哈希表在创建迭代器/枚举时或之后的某个时刻的状态的元素。

(着重号。)这并不明确处理forEach(),但我希望它的行为是通过Iterator在地图的条目集类似,可以实现的。也就是说,forEach()迭代将反映某些固定时间点的地图内容。因此,我认为完全没有必要假设forEach()可以看到其他线程对地图的修改。实际上,我预计其他线程的修改一般会被而不是反映在forEach()的行为中,尽管规范中有空间让它看到一些修改。

要提供地图的快照,您需要在给定位置复制地图。如果迭代器只是一个快照,它最初也必须创建一个副本。因为这会花费额外的内存和计算量,所以它不会这么做,然后存在体系结构的原因,为什么这可能不合需要。

从get(key)和相关访问方法返回的任何非空结果与相关联的插入或更新都具有发生之前的关系。任何批量操作的结果反映了这些每个元素的关系的组成

在这些线是(不是那么清楚)指出,任何GET(迭代器或单个呼叫)以前发生的任何变化都已经包含并通过获取操作来反映。因此,在任何给定的时间,一个forEach批量操作将在最新的地图状态下工作。

您已经在问题中给出了这个问题的唯一解决方案:在分发之前使用地图的复制构造函数创建本地快照。这是额外的内存开销,但这是获取快照的唯一方法。