在ConcurrentHashMap上使用forEach时,更新是否反映出来,或者它的行为是否像故障保护迭代器



如果我在ConcurrentHashMap上启动forEach操作,而其他线程仍在该映射上执行put,我会看到其他bin的新更新吗?

这样做的原因是,我正在努力找到最有效的方法,将ConcurrentHashMap的内容广播给侦听器,而不会对映射的新数据写入者造成争用。但是,当我通知侦听器时,我希望所有侦听器都能收到相同的Map快照。

它不是故障安全的,但更新并没有以您认为的方式反映出来;因此,如果你之前已经看到一个bin,你将不再从该bin获得更新。

如果你知道spread是如何在内部工作的,你甚至可以引起OOM(这是我从Holger回答的一个问题中的一个很好的评论,但我现在似乎找不到…)

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

API级文档有这样一句话:

检索操作(包括get)通常不阻塞,因此可能与更新操作(包括putremove)重叠。重试反映了最近完成的更新操作在开始时保持的结果。(更正式地说,给定密钥的更新操作与报告更新值的该密钥的任何(非null)检索具有先发生后发生的关系。)对于诸如putAllclear之类的聚合操作,并发检索可能只反映插入或删除一些条目类似地,IteratorsSpliteratorsEnumerations返回元素,反映在迭代器/枚举创建时或创建之后某个时刻哈希表的状态

(添加了强调。)这并没有明确地寻址forEach(),但我希望它的行为与通过映射的入口集上的Iterator实现的行为类似。也就是说,forEach()迭代将反映某个固定时间点的地图内容。因此,我认为forEach()将看到其他线程对映射的修改是不安全的。事实上,我希望其他线程的修改(通常是而不是)反映在forEach()的行为中,尽管规范中有允许它看到一些修改的空间。

要提供地图的快照,您需要在给定点复制地图。如果迭代器只是一个快照,那么它最初也必须创建一个副本。由于这需要额外的内存和计算,所以它不会这么做,而且无论如何,这可能是不可取的,这也是有架构原因的。

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

在这些行中(没有那么清楚)声明,在任何get(迭代器或单个调用)之前发生的任何更改都已经包括在内,并由该get操作反映。因此,forEach批量操作将在任何给定时间对映射的最新状态起作用。

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

相关内容

最新更新