Java并发修改错误HashMap

时间:2017-03-30 22:26:10

标签: java concurrency

这里有趣的问题。为什么第一个版本会抛出并发修改错误,而第二个版本则不会。这会发生吗?

Map<String,Integer> map = new HashMap<>();    
... // Populate the map
for(String key : map.keySet()){
    if(map.get(key) < 50){
        map.remove(key);
    }
}

Map<String,Integer> map = new HashMap<>();    
... // Populate the map
for(String key : new ArrayList<String>(map.keySet())){
    if(map.get(key) < 50){
        map.remove(key);
    }
}

3 个答案:

答案 0 :(得分:4)

第一个示例引发异常,因为您在迭代时修改了地图。这是预期的。

在第二个示例中,您将创建一个包含地图中所有字符串的ArrayList。在这里迭代新创建的ArrayList,所以你的第二个例子不会抛出一个例子,因为你遍历ArrayList,而不是遍历地图

答案 1 :(得分:0)

for(String key : map.keySet()){
    if(map.get(key) < 50){
        map.remove(key);
    }
}

将始终抛出ConcurrentModificationException,因为您在迭代时删除了项目。您需要的是具有Iterator操作的remove

for(Iterator<Map.Entry<String, Integer>> it = map.entrySet().iterator(); it.hasNext(); ) {
    Map.Entry<String, Integer> entry = it.next();
    if(entry.getValue() < 50) {
      it.remove();
    }
}

还有ConcurrentHashMap支持并发操作,只在需要时锁定桶。

答案 2 :(得分:0)

在第一种情况下,您会收到concurrentModificationException,因为在内部实现方面存在与迭代相关的修改计数。如果修改计数在迭代期间发生更改,则会引发concurrentModificaitonException。

解决方案是使用iterator.remove()而不是直接从地图中删除元素。

在第二种情况下,在从地图中删除元素时,您不是迭代地图而是迭代不同的集合。在这种情况下,修改计数永远不会在迭代期间更改 ,因为您正在迭代不同的集合。

此外,在多线程环境中,在迭代之前总是在表示类中共享可变状态的集合上使用synchronized,否则可能会遇到concurrentModificationException。

在多线程环境中,您的第二个解决方案不正确,因为您尚未同步将原始地图的键集转移到新集合的语句。因此有可能获得concurrentModificationException。在多线程环境中使用ConcurrentHashMap,同时知道并非默认情况下ConcurrentHashMap上的每个操作或操作集都是线程安全的。