【问题标题】:Receiving ConcurrentModificationException when attempting to iterate through HashMap尝试遍历 HashMap 时收到 ConcurrentModificationException
【发布时间】:2018-01-25 09:21:58
【问题描述】:

我正在处理的项目有问题。有一个主循环运行程序,每帧调用update() 方法。

我有一个对象是 World,其中包含 HashMap<ChunkPos, Chunk>,其中 ChunkPos 是一个数据结构,它的 xyz 位置代表 ChunkWorld 中的位置

我试图在我的更新函数中遍历我的世界中的所有块,因为每个块都有自己的 GameObject ArrayList,其中包含这个世界块中的所有对象。

public void update() {
    HashMap<ChunkPos, Chunk> chunkMap = world.getChunkMap();
    Iterator<Map.Entry<ChunkPos, Chunk>> it = chunkMap.entrySet().iterator();
    while(it.hasNext()) {
        Map.Entry<ChunkPos, Chunk> item = it.next();
        ArrayList<GameObject> chunkObjects = item.getValue().getObjects();
        for(GameObject obj : chunkObjects) {
            obj.update();
        }
    }
}

但是,当我运行程序时,我在这一行收到ConcurrentModificationException

Map.Entry<ChunkPos, Chunk> item = it.next();

我尝试了多种不同的方法来遍历 HashMap,但每次我都会遇到同样的错误。我做错了什么?

如果有帮助,这里是完整的 StackTrace:

Exception in thread "main" java.util.ConcurrentModificationException
at java.util.HashMap$HashIterator.nextNode(HashMap.java:1429)
at java.util.HashMap$EntryIterator.next(HashMap.java:1463)
at java.util.HashMap$EntryIterator.next(HashMap.java:1461)
at com.anzanama.lwjgl3d.Game.Game.update(Game.java:40)
at com.anzanama.lwjgl3d.Game.Game.loop(Game.java:31)
at com.anzanama.lwjgl3d.Main.main(Main.java:15)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)

只是想在此说明一下:我最终实现了一个 WorldChangeScheduler,它会在遍历整个世界并对其进行更新后安排对世界进行的更改。这使它更加健壮,并且具有额外的好处,我可以稍后返回并将这些 WorldChanges 转换为使用我的引擎的其他人可以挂钩的事件。

【问题讨论】:

  • 问题不在于对 HashMap 的迭代,而在于您在迭代它时其他事情正在改变地图。同步它或类似的东西。
  • 鉴于没有提及线程使用,可能的原因是在调用obj.update(); 中您正在修改HashMap。除非通过迭代器本身进行,否则在迭代集合时不能修改集合

标签: java hashmap iteration nested-lists


【解决方案1】:

这里ConcurrentModificationException表示在迭代过程中修改了地图,没有使用迭代器。

obj.update(); 修改 world.getChunkMap() 映射,或者您有另一个线程在当前 void update() 方法的迭代期间同时添加或删除同一映射的元素。
因为在显示的代码中没有对地图进行任何修改。

【讨论】:

  • 不过,我仍然找不到该问题的解决方案。我真的不知道在更新期间 chunkMap 会在哪里被修改。此外,该程序只有一个线程,因此这不是问题。目前,世界上唯一具有实际执行操作的更新函数的对象是我的 PlayerObject,它最初在 updateMovement() 函数期间对块进行更改。但是,我创建了一个“WorldChangeScheduler”,这样我就可以安排对世界进行更改,然后在遍历 hashmap 之后进行所有更改。
  • 没关系,我找到了问题所在。现在它只是运行非常缓慢。我猜我可能不得不从零开始
  • 可能。由于您有竞争条件,您必须尽可能减少关键部分时间(en.wikipedia.org/wiki/Critical_section),并且作为附加提示,如果在他们的两个客户端之间可以接受 Map 状态更新的延迟是可以接受的迭代器循环,您也可能更喜欢 ConcurrentHashMap 而不是 synchronized 语句/方法。
【解决方案2】:

好吧,这段代码造成了麻烦

ArrayList<GameObject> chunkObjects = item.getValue().getObjects();
for(GameObject obj : chunkObjects) {
    obj.update();
}

使用迭代器并调用更新

Iterator<GameObject> iter = item.getValue().getObjects().iterator();

while (iter.hasNext()) {
    GameObject obj = iter.next();
    obj.update();
}

【讨论】:

    【解决方案3】:

    HashMap 不是线程安全的,因此当其他线程在迭代时发生结构修改时,迭代器会很快失败。

    通过用 ConcurrentHashMap 替换您的 Map 实现,您将防止此类问题,但锁定将应用于更新操作,这将影响其性能。您可以使用 ConcurrentHashMap 的 concurrencyLevel 参数来配置并发级别,如果预期有可预测的线程数同时更新此 Map。

    【讨论】:

      猜你喜欢
      • 2023-03-10
      • 2011-10-23
      • 2015-11-21
      • 2017-05-30
      • 2013-03-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多