Как бороться с ConcurrentModificationException

Я получаю исключение ConcurrentModificationException из моего таймера восстановления. Я использую поток для уменьшения значений каждую секунду следующим образом:

public class CoolDownTimer implements Runnable {
    @Override
    public void run() {
        for (String s : playerCooldowns.keySet()) {
            playerCooldowns.put(s, playerCooldowns.get(s) - 20);
            if (playerCooldowns.get(s) <= 0) {
                playerCooldowns.remove(s);
            }
        }
    }

}

Таким образом, каждую секунду это должно уменьшать время восстановления каждого игрока на 20, но проблема в том, что я получаю CME каждые пару часов во время работы программы, особенно когда много людей в сети. Как мне сделать так, чтобы, если он все еще изменяет список, он ждал, пока текущая операция не будет выполнена, и создавал своего рода очередь модификации? Спасибо! Вот трассировка стека:

2012-06-18 20:59:05 [WARNING] Task of 'SurvivorsClasses' generated an exception
java.util.ConcurrentModificationException
at java.util.HashMap$HashIterator.nextEntry(HashMap.java:839)
at java.util.HashMap$KeyIterator.next(HashMap.java:874)
at me.zachoooo.survivorclasses.CoolDownManager$CoolDownTimer.run(CoolDownManager.java:13)
at org.bukkit.craftbukkit.scheduler.CraftScheduler.mainThreadHeartbeat(CraftScheduler.java:126)
at net.minecraft.server.MinecraftServer.w(MinecraftServer.java:533)
at net.minecraft.server.MinecraftServer.run(MinecraftServer.java:459)

Строка 13 - это начало цикла for...


person Zach Sugano    schedule 19.06.2012    source источник
comment
Вы не должны изменять свой HashMap одновременно, если вы хотите получить одновременный доступ, попробуйте использовать ConcurrentHashMap вместо HashMap.   -  person mprabhat    schedule 19.06.2012


Ответы (3)


Вы не можете изменять коллекции при использовании цикла foreach.

Однако вы можете перебрать Map.entrySet() и сделать все, что вам нужно:

public void run() {
    for (Iterator<Map.Entry<String,Integer>> i = playerCooldowns.entrySet().iterator(); i.hasNext();) {
        Map.Entry<String,Integer> entry = i.next();
        entry.setValue(entry.getValue() - 20); // update via the Map.Entry
        if (entry.getValue() <= 0) {
            i.remove(); // remove via the iterator
        }
    }
}
person Bohemian♦    schedule 19.06.2012
comment
я.удалить(); // удалить через итератор, это строка, которая делает свое дело - person Subin Sebastian; 19.06.2012

ConcurrentModificationException выбрасывается, когда вы пытаетесь изменить содержимое своего Collection, в то же время, пока Iterating через него.

Прочтите это и это для более подробного обсуждения.

Причина, по которой иногда это может сработать, четко указана в документация.

The iterators returned by all of this class's "collection view methods" are fail-fast: if 
the map is structurally modified at any time after the iterator is created, in any way 
except through the iterator's own remove method, the iterator will throw a 
ConcurrentModificationException. Thus, in the face of concurrent modification, the 
iterator fails quickly and cleanly, rather than risking arbitrary, non-deterministic 
behavior at an undetermined time in the future.

Note that the fail-fast behavior of an iterator cannot be guaranteed as it is, generally 
speaking, impossible to make any hard guarantees in the presence of unsynchronized 
concurrent modification. Fail-fast iterators throw ConcurrentModificationException on a 
best-effort basis.
person Kazekage Gaara    schedule 19.06.2012

Unlike Array, Collections проверяются только во время компиляции, НЕ во время времени выполнения, поэтому вы < strong>не может изменять Коллекцию, например, put() или remove() в цикле.

person Kumar Vivek Mitra    schedule 19.06.2012