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

问题描述 投票:0回答:4

我有一段简单的代码,它循环遍历映射,检查每个条目的条件,如果条件为真,则在条目上执行一个方法。之后该条目将从地图中删除。 要从地图中删除条目,我使用

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
    }
}
java swing hashmap concurrentmodification
4个回答
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());
    }

1
投票

当您在多个线程中操作对象时,请使用 ConcurrentHashMap 代替 HashMap。 HashMap 类不是线程安全的,也不允许此类操作。请参阅下面的链接以获取与此相关的更多信息。

https://www.google.co.in/amp/s/www.geeksforgeeks.org/difference-hashmap-concurrenthashmap/amp/

请告诉我更多信息。


0
投票

因为我已经在这里并且没有“完整”的答案,所以它是:

HashMap 类文档明确指出,如果多个线程访问

HashMap
实例并且至少一个在结构上修改了它,则必须在外部同步HashMap实例(通常使用
Collections.synchronizedMap
)。

请注意,此实现不是同步的。如果多个线程同时访问哈希映射,并且至少有一个线程在结构上修改映射,则它必须在外部同步。 (结构修改是添加或删除一个或多个映射的任何操作;仅更改与实例已包含的键关联的值不是结构修改。)这通常是通过在自然封装映射的某个对象上进行同步来完成的。如果不存在这样的对象,则应使用 Collections.synchronizedMap 方法“包装”地图。

它还说更改与键关联的值不是结构修改,但没有说明这些情况是否总是产生定义的行为(可能会发生竞争条件,IDK)。

现在是

ConcurrentModificationException
。当它可以知道在支持 Map 中执行了结构修改而不是通过这个特定的迭代器
remove()
方法进行时,它由
fail-fast 迭代器
引发。也就是说,当您在迭代器处于活动状态时更改支持
Map
(如果未使用迭代器
remove()
执行此修改)时,单线程程序中的迭代器也可能引发此异常。

来自 ConcurrentModificationException 文档

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

在您的情况下,如果程序中的一个线程从
HashMap

实例实例化一个迭代器只是为了读取,并且另一个线程也从同一个

Map
实例实例化一个迭代器,并且这些迭代器的生命周期重叠,如果一个迭代器调用
remove()
,那么其他人就可以举
ConcurrentModificationException
    


-1
投票

keySet() 允许您迭代键。这对你没有帮助,因为钥匙是 通常是不可变的。
如果您只想访问地图值,则需要使用

values() 。 如果它们是可变对象,则可以直接更改,无需放置 他们回到地图中。

entrySet() 最强大的版本,可让您更改条目的值 直接。

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

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

实际上,如果您只想编辑值对象,请使用值集合来完成。我假设你的地图属于 
:

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

	
© www.soinside.com 2019 - 2024. All rights reserved.