【发布时间】:2015-07-04 17:53:51
【问题描述】:
我想使用 ConcurrentHashMap 让一个线程定期从地图中删除一些项目,而其他线程同时从地图中放置和获取项目。
我在删除线程中使用map.entrySet().removeIf(lambda)。我想知道我可以对它的行为做出什么假设。我可以看到removeIf 方法使用迭代器遍历地图中的元素,检查给定条件,然后在需要时使用iterator.remove() 删除它们。
文档提供了一些关于 ConcurrentHashMap 迭代器行为的信息:
类似地,迭代器、拆分器和枚举返回元素 反映哈希表在某个时刻或之后的状态 创建迭代器/枚举。嘿,不要抛出 ConcurrentModificationException。但是,迭代器被设计为一次只能由一个线程使用。
由于整个removeIf 调用发生在一个线程中,我可以确定迭代器当时没有被多个线程使用。我仍然想知道下面描述的事件过程是否可能:
- 映射包含映射:
'A'->0 - 删除线程开始执行
map.entrySet().removeIf(entry->entry.getValue()==0) - 在
removeIf调用中删除线程调用.iteratator()并获取反映集合当前状态的迭代器 - 另一个线程执行
map.put('A', 1) - 删除线程仍会看到
'A'->0映射(迭代器反映旧状态)并且因为0==0为真,它决定从映射中删除A 键。 - 地图现在包含
'A'->1,但删除线程会看到0的旧值,并且'A' ->1条目被删除,即使它不应该被删除。地图是空的。
我可以想象这种行为可以通过多种方式被实现所阻止。例如:迭代器可能不反映放置/删除操作,但总是反映值更新,或者迭代器的 remove 方法在调用键上的 remove 之前检查整个映射(键和值)是否仍然存在于映射中。我找不到有关发生的任何这些事情的信息,我想知道是否有什么东西可以使该用例安全。
【问题讨论】:
-
在所有java集合中,迭代器都是由原始数据集支持的,所以数据的变化会立即被迭代器反映,这通常会抛出ConcurrentModificationException。并发集合是不同的,因为迭代器是故障安全的,这意味着它们可以处理对底层集合的修改。但是,它们仍然在大多数实现中迭代实时数据。
-
如here 所述,唯一可以保证的是迭代器所遍历的元素在迭代器构建时存在于映射中。这些元素可能不再存在,或者在您使用迭代器访问它们时可能会被更改。我的建议是您仅将迭代器用作可能性指示,但直接在地图上执行所有操作(例如,使用
map.remove(key,value)而不是iterator.remove())。
标签: java multithreading java.util.concurrent concurrenthashmap concurrentmodification