使用带有Maps键集的流时出现ConcurrentModificationException

时间:2015-09-15 08:04:03

标签: java foreach lambda hashmap java-stream

我想从someMap中删除someList中不存在哪些密钥的所有项目。看看我的代码:

someMap.keySet().stream().filter(v -> !someList.contains(v)).forEach(someMap::remove);

我收到了java.util.ConcurrentModificationException。为什么?流不是平行的。最优雅的方法是什么?

4 个答案:

答案 0 :(得分:28)

@Eran已经explained如何更好地解决这个问题。我将解释为什么会出现ConcurrentModificationException

发生ConcurrentModificationException是因为您正在修改流源。您的Map可能是HashMapTreeMap或其他非并发地图。我们假设它是HashMap。每个流都由Spliterator支持。如果分词器没有IMMUTABLECONCURRENT特征,那么,正如文档所述:

  

绑定Spliterator后,如果检测到结构性干扰,应尽可能地抛出ConcurrentModificationException。执行此操作的Spliterators称为 fail-fast

因此HashMap.keySet().spliterator()不是IMMUTABLE(因为此Set可以修改)而不是CONCURRENT(并发更新对HashMap不安全)。所以它只是检测并发更改并抛出ConcurrentModificationException作为spliterator文档规定。

还值得引用HashMap文档:

  

所有类的“集合视图方法”返回的迭代器都是 fail-fast :如果在创建迭代器之后的任何时候对映射进行结构修改,除了通过迭代器之外的任何方式自己的remove方法,迭代器将抛出ConcurrentModificationException。因此,面对并发修改,迭代器会快速而干净地失败,而不是在未来不确定的时间冒着任意的,非确定性行为的风险。

     

请注意,迭代器的快速失败行为无法得到保证,因为一般来说,在存在非同步并发修改的情况下,不可能做出任何硬性保证。失败快速的迭代器会尽最大努力抛出ConcurrentModificationException。因此,编写依赖于此异常的程序以确保其正确性是错误的:迭代器的故障快速行为应仅用于检测错误

虽然它只说迭代器,但我相信分裂器也是如此。

答案 1 :(得分:13)

您不需要Stream API。在retainAll上使用keySetSet返回的keySet()上的任何更改都会反映在原始Map中。

someMap.keySet().retainAll(someList);

答案 2 :(得分:9)

你的流调用(逻辑上)与:

相同
for (K k : someMap.keySet()) {
    if (!someList.contains(k)) {
        someMap.remove(k);
    }
}

如果你运行它,你会发现它抛出ConcurrentModificationException,因为它在你迭代它的同时修改了地图。如果您查看docs,您会注意到以下内容:

  

请注意,此异常并不总是表示某个对象已被另一个线程同时修改。如果单个线程发出违反对象合同的一系列方法调用,则该对象可能会抛出此异常。例如,如果线程在使用失败快速迭代器迭代集合时直接修改集合,则迭代器将抛出此异常。

这就是你正在做的事情,你正在使用的地图实现显然具有快速失败的迭代器,因此抛出了这个异常。

一种可能的替代方法是直接使用迭代器删除项目:

for (Iterator<K> ks = someMap.keySet().iterator(); ks.hasNext(); ) {
    K next = ks.next();
    if (!someList.contains(k)) {
        ks.remove();
    }
}

答案 3 :(得分:2)

稍后的答案,但是您可以在管道中插入一个收集器,以便forEach在Set上进行操作,该Set包含键的副本:

someMap.keySet()
    .stream()
    .filter(v -> !someList.contains(v))
    .collect(Collectors.toSet())
    .forEach(someMap::remove);
相关问题