为什么我在同时迭代和修改HashMap时没有收到“ConcurrentModificationException”异常?

4
我有一张地图。
Map<String, String> map = new HashMap<String, String>();

map.put("Pujan", "pujan");
map.put("Swati", "swati");
map.put("Manish", "manish");
map.put("Jayant", "jayant");
Iterator<Map.Entry<String, String>> itr = map.entrySet().iterator();
while(itr.hasNext()){
  Entry<String,String> entry=(Entry<String, String>) itr.next();
  map.put("Manish", "Updated");
}

在这里我没有遇到异常(我正在尝试修改现有的键值“Manish”)。但是,如果我尝试添加一个新的键 map.put("Manish123", "Updated"),就会出现 ConcurrentModificationException 异常。


1
只要不弄乱键(添加或删除键),因为你正在遍历键,所以应该没问题。 - tt_emrah
3个回答

4

由于您没有修改iterator,因此在这种情况下put将改变现有条目,因为在Map中已经存在具有相同键的Map.Entry


0

如果您查看Java 8 HashMap.java源代码中HashMap的 modCount 字段的Javadoc,您将会看到:

/**
 * The number of times this HashMap has been structurally modified.
 * Structural modifications are those that change the number of mappings in
 * the HashMap or otherwise modify its internal structure (e.g.,
 * rehash).  This field is used to make iterators on Collection-views of
 * the HashMap fail-fast.  (See ConcurrentModificationException).
 */

因此,该字段保留了地图结构修改的次数。该类中的各种迭代器在预期的修改计数expectedModCount(例如,在构造此迭代器时初始化为modCount,例如,在行Iterator<Map.Entry<String, String>> itr = map.entrySet().iterator();处)与modCount不匹配时,会抛出ConcurrentModificationException(可以选择更好的名称),而modCount在对地图进行结构修改时(例如,调用put添加新条目等)会发生变化。请注意,这里没有涉及不同的线程。所有这些都可以在一个单独的线程中发生,例如,在迭代时从地图中删除条目或向其中添加条目。

正如您现在理解的那样,将现有键重新映射到不同值不应导致哈希映射的内部结构发生更改(因为它只是替换与键关联的值)。而您要做的就是简单地将键 Manish 重复映射到一个值 Updated,次数等于映射中的条目数(即4个,在迭代期间保持不变)。但是,如果添加或删除任何键,则会出现 ConcurrentModificationException

这类似于以下代码(注:仅用于说明目的):

    List<String> names = Arrays.asList("Larry", "Moe", "Curly");
    int i = 0;
    Iterator<String> strIter = names.iterator();
    while (strIter.hasNext()) {
        names.set(i, strIter.next() + " " + i); // value changed, no structural modification to the list
        i += 1;
    }
    System.out.println(names);

输出以下内容:

[Larry 0, Moe 1, Curly 2]

-1
根据Java API:如果在迭代开始后修改了集合,则使用Iterator迭代集合会导致ConcurrentModificationException,但这仅发生在快速失败的迭代器中。
Java中有两种类型的迭代器,即快速失败和安全失败,请查看快速失败和安全失败迭代器之间的差异以获取更多详细信息。 Java中的快速失败迭代器 正如名称所示,快速失败迭代器会在它们意识到集合结构已经在迭代开始后被更改时立即失败。结构性更改意味着在一个线程正在迭代该集合时添加、删除或更新任何元素。快速失败行为是通过保持修改计数来实现的,如果迭代线程意识到修改计数的变化,它会抛出ConcurrentModificationException。
Java文档表示,这不是一种保证的行为,而是基于“尽力而为”的原则进行的。因此,应用程序编程不能依赖于此行为。另外,由于在更新和检查修改计数时涉及多个线程,并且此检查是在没有同步的情况下完成的,因此迭代线程仍然可能看到旧值,并且可能无法检测到并行线程所做的任何更改。 JDK1.4集合返回的大多数迭代器都是快速失败的,包括Vector、ArrayList、HashSet等。 Java中的安全失败迭代器 与 fail-fast 迭代器相反,fail-safe 迭代器在集合在一个线程正在迭代时被结构性修改时不会抛出任何异常,因为它们使用集合的克隆而不是原始集合进行操作,这就是它们被称为 fail-safe 迭代器的原因。CopyOnWriteArrayList 的迭代器是 fail-safe 迭代器的一个例子,ConcurrentHashMap keySet 写的迭代器也是 fail-safe 迭代器,并且在 Java 中永远不会抛出 ConcurrentModificationException 异常。

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