我想使用 ConcurrentHashMap 让一个线程定期从 map 中删除一些项目,同时让其他线程从 map 中放置和获取项目。
我在删除线程中使用 map.entrySet().removeIf(lambda)
。我想知道我可以对其行为做出哪些假设。我可以看到 removeIf
方法使用迭代器遍历映射中的元素,检查给定条件,然后在需要时使用 iterator.remove()
删除它们。
文档提供了一些关于 ConcurrentHashMap 迭代器行为的信息:
Similarly, Iterators, Spliterators and Enumerations return elements reflecting the state of the hash table at some point at or since the creation of the iterator/enumeration. hey do not throw ConcurrentModificationException. However, iterators are designed to be used by only one thread at a time.
由于整个 removeIf
调用发生在一个线程中,因此我可以确定迭代器不会同时被多个线程使用。我仍然想知道下面描述的事件过程是否可能:
- Map 包含映射:
'A'->0
- 删除线程开始执行
map.entrySet().removeIf(entry->entry.getValue()==0)
- 删除线程在
removeIf
调用中调用.iteratator()
并获取反射(reflect)集合当前状态的迭代器 - 另一个线程执行
map.put('A', 1)
- 删除线程仍然看到
'A'->0
映射(迭代器反射(reflect)旧状态)并且因为0==0
为真,它决定从中删除 A 键 map 。 - map 现在包含
'A'->1
但删除线程看到旧值0
和'A' ->1
条目被删除,即使它不应该被删除。 map 是空的。
我可以想象,这种行为可能会被实现以多种方式阻止。例如:迭代器可能不反射(reflect)放置/删除操作,但始终反射(reflect)值更新,或者迭代器的删除方法可能在调用键上的删除之前检查整个映射(键和值)是否仍然存在于映射中。我找不到有关发生的任何事情的信息,我想知道是否有什么东西可以使该用例安全。
最佳答案
我也设法在我的机器上重现了这种情况。
我认为,问题是 EntrySetView
(由 ConcurrentHashMap.entrySet()
返回)从 Collection< 继承了它的
,它看起来像:removeIf
实现
default boolean removeIf(Predicate<? super E> filter) {
Objects.requireNonNull(filter);
boolean removed = false;
final Iterator<E> each = iterator();
while (each.hasNext()) {
// `test` returns `true` for some entry
if (filter.test(each.next())) {
// entry has been just changed, `test` would return `false` now
each.remove(); // ...but we still remove
removed = true;
}
}
return removed;
}
以我的拙见,这不能被视为 ConcurrentHashMap
的正确实现。
关于java - ConcurrentHashMap 中 entrySet().removeIf 的行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29876083/