使用迭代器删除条目时出现ConcurrentModificationException异常

3

我有一段简单的代码,它遍历一个映射表,在每个条目上检查一个条件,如果该条件为真,则执行该条目的方法。之后,该条目将从映射表中移除。 为了从映射表中删除一个条目,我使用了Iterator来避免ConcurrentModificationException异常。

但是我的代码确实在it.remove()这行抛出了异常:

Caused by: java.util.ConcurrentModificationException
    at java.util.HashMap$HashIterator.remove(Unknown Source) ~[?:1.8.0_161]
    at package.Class.method(Class.java:34) ~[Class.class:?]

经过长时间的搜索,我找不到解决方法,所有答案都建议使用 Iterator.remove() 方法,但我已经在使用它了。 Map.entrySet() 的文档清楚地指出,可以使用 Iterator.remove() 方法从集合中删除元素。

非常感谢您的帮助。

我的代码:

Iterator<Entry<K, V>> it = map.entrySet().iterator();
while (it.hasNext()) {
    Entry<K, V> en = it.next();

    if (en.getValue().shouldRun()) {
        EventQueue.invokeLater(()->updateSomeGui(en.getKey())); //the map is in no way modified in this method
        en.getValue().run();
        it.remove(); //line 34
    }
}

2
但是你正在另一个线程中调用 en.getKey。这肯定不是线程安全的。 - UninformedUser
1
@Eran EventQueue.invokeLater确实会这样做 - 就像我所说的那样。 - UninformedUser
不太确定是否有效,但请检查您是否正在使用ConcurrentHashMap或普通的HashMap,如果尚未使用并发的,请使用并发的。 - Rohit Dodle
1
@AKSW en.getKey() 不会改变 Map。 - Eran
@AKSW @Eran,没有任何可能会改变地图的线程。注释掉EventQueue这一行也没有帮助。 - superbadcodemonkey
显示剩余7条评论
3个回答

2
如果您无法将 HashMap 更改为 ConcurrentHashMap,则可以使用另一种方法来修改您的代码。
您可以创建一个包含您想要删除的条目的条目列表,然后遍历它们并从原始映射中删除它们。
例如:
    HashMap<String, String> map = new HashMap<>();
    map.put("1", "a1");
    map.put("2", "a2");
    map.put("3", "a3");
    map.put("4", "a4");
    map.put("5", "a5");
    Iterator<Map.Entry<String, String>> iterator = map.entrySet().iterator();
    List<Map.Entry<String, String>> entries = new ArrayList<>();

    while (iterator.hasNext()) {
        Map.Entry<String, String> next = iterator.next();
        if (next.getKey().equals("2")) {
            /* instead of remove
            iterator.remove();
            */
            entries.add(next);
        }
    }

    for (Map.Entry<String, String> entry: entries) {
        map.remove(entry.getKey());
    }

通常情况下,我会使用CopyOnWriteArrayList来复制数组列表。https://dev59.com/jXA85IYBdhLWcg3wF_YO - ecle

1

但在这种情况下,这是没有意义的,因为地图从另一个线程中无法被修改。理论上可能会有另一个线程从中读取,但这并不能解释“ConcurrentModificationException”的出现。 然而,这段代码位于包含一些“黑魔法”asm内容的相当大的系统中,所以我想某种方式成功地对其进行了修改。 尽管如此,我仍然接受它,因为它确实解决了我的问题。 - superbadcodemonkey
Oracle文档中的异常类可能会对此有所帮助。它不一定要完全修改...即使只是在另一个线程中读取,也可能会引发此错误。引用文档中的话:“例如,在一个线程遍历集合时,通常不允许另一个线程修改该集合。” - Rohit Dodle
Oracle的文档:https://docs.oracle.com/javase/7/docs/api/java/util/ConcurrentModificationException.html - Rohit Dodle

-1

为此,您应该使用集合视图来公开地映射:

keySet() 允许您迭代键。但这对您没有帮助,因为键通常是不可变的。

如果您只想访问映射值,则需要使用 values()。如果它们是可变对象,则可以直接更改,无需将它们放回到映射中。

entrySet() 是最强大的版本,允许您直接更改条目的值。

示例:将所有包含下划线的键的值转换为大写

for(Map.Entry<String, String> entry:map.entrySet()){
    if(entry.getKey().contains("_"))
        entry.setValue(entry.getValue().toUpperCase());
}

实际上,如果您只想编辑值对象,请使用values集合进行操作。我假设您的映射类型为<String,Object>:

for(Object o: map.values()){
    if(o instanceof MyBean){
        ((Mybean)o).doStuff();
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接