ConcurrentHashMap

时间:2015-04-26 09:48:26

标签: java multithreading java.util.concurrent concurrenthashmap concurrentmodification

我想使用ConcurrentHashMap让一个线程定期从地图中删除一些项目,并使用其他线程同时从地图中放置和获取项目。

我在删除线程中使用map.entrySet().removeIf(lambda)。我想知道我可以对其行为做出什么假设。我可以看到removeIf方法使用迭代器遍历地图中的元素,检查给定条件,然后在需要时使用iterator.remove()删除它们。

文档提供了有关ConcurrentHashMap迭代器行为的一些信息:

  

同样,Iterators,Spliterators和Enumerations返回元素   反映哈希表的状态在某个时刻或之后的某个时刻   创建迭代器/枚举。嘿不要抛出ConcurrentModificationException。但是,迭代器设计为一次只能由一个线程使用。

当整个removeIf调用在一个线程中发生时,我可以确定迭代器当时不被多个线程使用。我仍然想知道下面描述的事件是否可能:

  1. 地图包含地图:'A'->0
  2. 删除线程开始执行map.entrySet().removeIf(entry->entry.getValue()==0)
  3. .iteratator()调用中删除线程调用removeIf并获取反映集合当前状态的迭代器
  4. 另一个线程执行map.put('A', 1)
  5. 删除线程仍然看到'A'->0映射(迭代器反映旧状态),因为0==0为真,它决定从地图中删除A键。
  6. 地图现在包含'A'->1,但删除线程会看到0的旧值,并且'A' ->1条目已删除,即使它不应该被删除。地图是空的。
  7. 我可以想象,实施可以通过多种方式阻止这种行为。例如:可能迭代器不反映put / remove操作但总是反映值更新,或者迭代器的remove方法可能会检查整个映射(键和值)是否仍然存在于映射中,然后才调用key上的remove。我找不到任何有关这些事情的信息,我想知道是否有一些东西可以使用例安全。

2 个答案:

答案 0 :(得分:10)

我还设法在我的机器上重现这种情况。 我认为,问题是EntrySetView(由ConcurrentHashMap.entrySet()返回)继承了removeIf的{​​{1}}实现,它看起来像:

Collection

我认为,这不能被视为 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; } 的正确实施。

答案 1 :(得分:7)

在用户Zielu与Zielu的回答讨论后,我已经深入研究了ConcurrentHashMap代码并发现:

  • ConcurrentHashMap实现提供调用remove(key, value)
  • replaceNode(key, null, value)方法
  • replaceNode检查在删除之前地图中是否仍然存在键和值,因此使用它应该没问题。文档说它
  

用v替换节点值,条件是匹配cv if        *非null。

  • 在问题中提到的ConcurrentHashMap的.entrySet()被调用,返回EntrySetView类。然后removeIf方法调用.iterator(),返回EntryIterator
  • EntryIterator扩展BaseIterator并继承调用remove的{​​{1}}实现,该实现禁用条件删除并始终删除密钥。

如果迭代器总是迭代“当前”值并且如果某些值被修改则永远不返回旧值,则仍然可以阻止事件的消极过程。我仍然不知道是否发生了这种情况,但下面提到的测试用例似乎验证了整个问题。

我认为这创建了一个测试用例,表明我的问题中描述的行为确实可以发生。如果我的代码中有任何错误,请纠正我。

代码启动两个线程。其中一个(DELETING_THREAD)删除映射到'false'布尔值的所有条目。另一个(ADDING_THREAD)随机将map.replaceNode(p.key, null, null)(1, true)值放入地图中。如果它将(1,false)置于值中,则它期望在检查时该条目仍然存在,如果不是则抛出异常。当我在本地运行时,它会快速抛出异常。

true