ConcurrentModificationException when iterate through ImmutableMap.copyof()

Tags: , ,



Currently, I have a problem when I frequently iterating through a HashMap (1-time per second)

I add new element to the map on the main thread and iterate the map on the other thread

I return an ImmutableMap.copyOf() to iterate through it, and sometimes I add a new element to the map. But it throws me a ConcurrentModificationException

java.util.ConcurrentModificationException: null
  at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:911) ~[?:1.8.0_261]
    at java.util.ArrayList$Itr.next(ArrayList.java:861) ~[?:1.8.0_261]

My pseudo-code:

class Foo {
    private final Map<String, List<User>> userMap = new HashMap<>();

    public static void addUser(String houseId, User user) {
        if(!userMap.containsKey(houseId)) {
            userMap.put(houseId, new ArrayList<>());
        }
        List<User> list = userMap.get(houseId);
        list.add(user);
    }

    public static Map<String, List<User>> getAll() {
        return ImmutableMap.copyOf(userMap);
    }
}

// iterating task
// this task run one per second

class Task extends TimerTask {
    public void run() {
        for (List<User> listUser : Foo.getAll().values()) {
            for (User user : listUser) {
                user.sayHello(); // print hello
            }
        }
    }
}

I thought that use an ImmutableMap.copyOf() will prevent me from this problem. Because I read that you should use an immutable copy of your list/map when iterating through it from other thread.

I think I “solve” this by using CopyOnWriteArrayList. But I’m really curious why it throws me an error.

Thank you!!!

Answer

As you can see in the stack trace, the error occurs in the ArrayList, not the Map. In method addUser, an element is added to the list via list.add(user);

This operation might happen, while another thread loops over the elements with for (User user : listUser) causing the error.

When you exchange the ArrayList with a CopyOnWriteArrayList, the list.add(user) internally makes a copy and adds the new user into the copy. Therefore, a potentially running iteration is not affected since it loops over the old version of the array. So the running iteration will ignore the new value. Any new iterator will include the new value.



Source: stackoverflow