3

I am having issues with a project I am working on. There is a main loop that runs the program which calls the update() method every frame.

I have an object that is a World which contains a HashMap<ChunkPos, Chunk> where a ChunkPos is a data structure that has an xyz position which represents a Chunk's position in the World

I am attempting to iterate through all the Chunks in my the world in my update function, as each Chunk has it's own ArrayList of GameObject that has all of the objects inside this chunk of the world.

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();
        }
    }
}

However, when I run the program, I am receiving a ConcurrentModificationException at this line:

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

I've tried multiple different ways of iterating through the HashMap, but each time I end up with this same error. What am I doing wrong?

Here is the full StackTrace if it helps:

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)

Just wanted to make a note here: I eventually ended up implementing a WorldChangeScheduler that would schedule changes to be made to the world once it has iterated through the whole world and updated it. This makes it more robust and has the added benefit that I can later go back and turn these WorldChanges into events that other people using my engine can hook into.

Andrew Graber
  • 177
  • 4
  • 12
  • The problem isn't in the iteration over the HashMap, but that other things are changing the map while you're iterating over it. Synchronize on it or the like. – Louis Wasserman Aug 17 '17 at 05:12
  • https://stackoverflow.com/questions/8104692/how-to-avoid-java-util-concurrentmodificationexception-when-iterating-through-an – Erwin Bolwidt Aug 17 '17 at 05:16
  • Given that there is no mention of Thread use, the likely cause is that inside the call `obj.update();` you're modifying the HashMap. You can't modify a collection while you are iterating over it unless you do it through the Iterator itself – Erwin Bolwidt Aug 17 '17 at 05:18

3 Answers3

4

Here ConcurrentModificationException means that the map was modified during iteration without using the iterator.

Either obj.update(); modifies the world.getChunkMap() map or you have another thread that concurrently adds or removes an element of this same map during the iteration of the current void update() method.
Because in the shown code there is not any modification of the map.

davidxxx
  • 125,838
  • 23
  • 214
  • 215
  • I still can't find a solution to the issue, though. I don't really see where the chunkMap would be getting modified during update. Also, there is only a single thread for the program so that couldn't be the issue. Currently, the only object in the world that has an update function that actually does something is my PlayerObject, which originally was making changes to chunks during it's `updateMovement()` function. However, I created a "WorldChangeScheduler" so that I could schedule a change to the world to be made, then all the changes are made after it's done iterating through the hashmap. – Andrew Graber Aug 17 '17 at 22:53
  • Here is a link to my code: https://github.com/AndrewGraber/LWJGL3D/blob/master/src/com/anzanama/lwjgl3d/GameObject/PlayerObject.java#L39 – Andrew Graber Aug 17 '17 at 23:07
  • Nevermind, I figured out the issue. Now it just runs extremely slowly. Guess i'm probably gonna have to restart from zero – Andrew Graber Aug 17 '17 at 23:27
  • Probably. As you have race conditions, you have to reduce reduce as much as possible the critical section time (https://en.wikipedia.org/wiki/Critical_section) and as an additional hint, If a delay in the update of the state of the Map is acceptable between two clients of them during iterator loops, you may also favor ConcurrentHashMap over `synchronized` statements/methods. – davidxxx Aug 18 '17 at 06:11
1

Well, this code is causing the trouble

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

Use an Iterator and call update

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

while (iter.hasNext()) {
    GameObject obj = iter.next();
    obj.update();
}
nagendra547
  • 5,672
  • 3
  • 29
  • 43
0

HashMap is not thread-safe, so iterators fail fast when structural modifications happen by other threads, while iterating.

By replacing your Map implementation with ConcurrentHashMap, you will prevent such issues, but locking will apply on the update operations, which will affect their performance. You can configure concurrency level, thought, using the concurrencyLevel parameter of the ConcurrentHashMap, if a predictable number of threads is expected to update this Map concurrently.