在 hashmap 中循环时出现并发修改错误

getting concurrentmodification error while looping on hashmap

我有以下错误:

Exception in thread "Thread-0" java.util.ConcurrentModificationException
at java.base/java.util.HashMap$HashIterator.nextNode(HashMap.java:1584)
at java.base/java.util.HashMap$KeyIterator.next(HashMap.java:1607)
at Server.run(Server.java:149)
at java.base/java.lang.Thread.run(Thread.java:832)

参考这段代码:

    for (Session key : sessions.keySet()) {
                        if (key.getPort2() != port && key.getPort1() != port) { // change later to ip
                            System.out.println("2nd time init 2 client");

                            session.setIp2(ip);
                            session.setPort2(port);

                            sessionID++;

                            sessions.put(session, sessionID);

                            // reset session
                            session = null;

                        }
                    }

你能解释一下为什么会这样吗?

java.util.HashMap 不是线程安全的,如果你正在遍历 HashMap 元素,同时如果你试图修改它,JVM 将通过 ConcurrentModificationException。您可以通过 Iterator 迭代 HashMap 元素,也可以使用 ConcurrentHashMap 代替。

您在迭代其内容的同时修改地图,这是被禁止的。

几乎所有 collection 类 都出于充分的理由禁止这样做。让我们假设你有一个 collection 其中包含

a b c g h i j k l

现在您遍历元素并在处理元素 k 时插入 d。您是否期望 d 被跳过,因为您已经超出了那个位置?如果你在b位置插入一个m怎么办?你希望 m 会被处理吗?或者您是否期望新元素不会被处理,因为它们在您开始迭代时不存在?

如果 Java 允许这样做,人们会抱怨意外行为。这就是 Java 不允许的原因。与旧的编程语言相比,Java 的主要目标之一是避免意外行为。

要解决此问题,请在开始迭代之前创建第二个临时空地图。然后在您的循环中,您可以将新元素放入该临时地图中。最后把它们组合起来:

Map<String, String> tmp=new HashMap<>();
for (Session key : sessions.keySet()) {
   ...
   tmp.put(session, sessionID);
}
sessions.putAll(tmp);

这样就很清楚发生了什么,没有意外行为。

HashMap 及其 entrySeykeySetvalues 方法返回的集合在迭代时不允许更改(插入、删除)。您可以创建一个临时列表来循环,同时仍然更改原始地图:

for (Session key : new ArrayList<>(sessions.keySet())) {
    ...
    sessions.put(session, sessionID);
    ...
}

您也可以创建一个临时列表来存储要插入的元素(在循环之后)。


不是问题,但不确定该部分的用途:

// reset session
session = null;

有点危险 -> NullPointerException 在接下来的迭代中(假设 if 块被执行)