Java并发修改错误HashMap

Java并发修改错误HashMap,java,concurrency,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

有趣的问题。为什么第一个版本会引发并发修改错误,而第二个版本不会。这种情况会发生吗

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);
    }
}
Map Map=newhashmap();
... // 填充地图
for(字符串键:map.keySet()){
如果(地图获取(键)<50){
地图。删除(键);
}
}
Map Map=newhashmap();
... // 填充地图
for(字符串键:新ArrayList(map.keySet())){
如果(地图获取(键)<50){
地图。删除(键);
}
}

第一个示例引发异常,因为您在迭代映射时修改了映射。这是意料之中的

在第二个示例中,创建一个包含映射中所有字符串的ArrayList。在这里,您迭代新创建的ArrayList,因此第二个示例不会抛出示例,因为您迭代的是ArrayList,而不是映射

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

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

在第一种情况下,您会得到一个concurrentModificationException,因为在内部实现方面,有一个与迭代相关联的修改计数。如果修改计数在迭代过程中发生更改,将抛出ConcurrentModificaitoneException

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

在第二种情况下,在从映射中删除元素时,您不是在迭代映射,而是在迭代另一个集合。在这种情况下,修改计数在迭代过程中不会改变,因为您正在迭代不同的集合

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


在多线程环境中,第二种解决方案可能不正确,因为您没有同步将原始映射的键集传输到新集合的语句。所以有可能得到concurrentModificationException。在多线程环境中使用ConcurrentHashMap,同时知道默认情况下ConcurrentHashMap上的每个操作或操作集都不是线程安全的。

可能是因为第二个使用Java.util.iterator,对于第一个方法,您基本上是在创建一个
迭代器
,如果您对某些类型的
集合
有一个
迭代器
,那么对
集合
的访问是无效的。我不确定我是否正确地记住了这一点
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();
    }
}