Вся проблема в ConcurrentModificationException

  • Автор темы Автор темы molor
  • Дата начала Дата начала

molor

Активный участник
Пользователь
Баллы
66
Привет.

У меня есть List<String>, содержащий случайное количество UUID'ов. Ну не суть, что он там содержит.
Каждые пять тиков я прохожу по этому массиву с помощью for:

Java:
for (String string : list.keySet()) {
// do Something..
}

При соблюдении некоторых условий, внутри этого for происходит вызов функции function(тот самый string из for);, внутри которой завершающим этапом следует такой код:

Java:
list.remove(тот самый string из for, переданный function() в качестве аргумента);

Временами на первой строке первого примера кода выбрасывает ConcurrentModificationException на вызов list.keySet(). Каким образом можно решить эту проблему? Что я упустил или чего не знаю?

Я знаю про Iterator, но использовать его не могу, так как функция function(string);, в которой выполняется удаление из list, вызывается не только в вышепоказанном цикле, но и в одиночку на любом из элементов массива list по желанию игрока - а там listIterator.remove(); не прокатит.
 
Не знаю, зачем @alexandrage удалил три своих сообщения, но он натолкнул меня на мысль использовать ConcurrentHashMap вместо HashMap. Вроде бы помогло, но тему пока оставлю открытой. Вдруг я ошибся.
 
Когда ты создаешь foreach-цикл, создается неявный итератор, который используется для прохода по List.Когда ты одновременно используешь итератор(который имеет фиксированные параметры на счёт листа, который он итерирует, thread-safe) и любой другой способ изменения листа, не относящийся к итератору, то возникает ConcurrentModificationException.Советую создать другой метод(function), который бы мог работать с итератором.
ConcurrentHashMap в этом случае не совсем верный вариант.Такой вид коллекции используется в мультипоточных средах, к тому же его использования лучше избегать, так как он медленнее.Почти все коллекции в Java потоконебезопасны, потому что в случае с synchronized коллекция обрабатывается поочерёдно, а значит медленно.В твоем же случае, как я выше написал, лист обрабатывается неявным итератором и методом непосредственно из самого List, отсюда и конкуренция.Например: Ты создал цикл foreach, он создал неявный итератор, итератор понял, что в листе есть n элементов, за время итерации удалился 1 элемент, теперь у нас размер листа n-1, а итератор всё ещё думает что n, тогда и выбрасывается такая ошибка.
 
Например: Ты создал цикл foreach, он создал неявный итератор, итератор понял, что в листе есть n элементов, за время итерации удалился 1 элемент, теперь у нас размер листа n-1, а итератор всё ещё думает что n, тогда и выбрасывается такая ошибка.
хмм, странно, я в таких случаях просто outOfBounds ловил
 
Как вариант, ты можешь использовать обычный цикл for, так как неявный итератор не создается, но тогда возможен ArrayOutOfBoundsException.
Как второй вариант, можешь создать репликацию листа, создать foreach на нём(и соотв. проходить по нему) а изменять первый лист, но сам понимаешь, этот вариант "не совсем" хорош.
хмм, странно, я в таких случаях просто outOfBounds ловил
Зависит от ситуации, возможно, ты проходил по entrySet'y, получая значение и проводя изменения в массиве используя значение как ключ.Например, ты проходишь по массиву, получаешь значение 5, а потом пытаешься получить элемент по индексу 5 или по ключу 5, а там такого не оказывается, тогда и вылетает ArrayOutOfBoundsException.
 
Как вариант, ты можешь использовать обычный цикл for, так как неявный итератор не создается, но тогда возможен ArrayOutOfBoundsException.
Как второй вариант, можешь создать репликацию листа, создать foreach на нём(и соотв. проходить по нему) а изменять первый лист, но сам понимаешь, этот вариант "не совсем" хорош.

Зависит от ситуации, возможно, ты проходил по entrySet'y, получая значение и проводя изменения в массиве используя значение как ключ.Например, ты проходишь по массиву, получаешь значение 5, а потом пытаешься получить элемент по индексу 5 или по ключу 5, а там такого не оказывается, тогда и вылетает ArrayOutOfBoundsException.
да, да я там просто форку запускал по размеру. сорре :oops:
 
Я решил эту проблему так
Java:
for(String str : ImmutableList.copyOf(list.keySet()))

P.S. ImmutableList - класс из библиотеки Guava, которая уже включена в ядро, так что придётся подключить его вместо обычного API баккита
 
HashSet<String> toRemove = new HashSet<>();
for (String s : ...) {
if (...)
toRemove.add(s);
}
toRemove.forEach(s -> { ...remove(s); });
toRemove.clear();
 
Я вообще пользуюсь говнокодом.
Делаю копию списка(s2). Нормальный список(s1)
Так вот, копию я такой сделал, перебираю s2 в цикле, а в s1 убираю...........
 
Два быстрых способа проитерировать коллекцию и удалить некоторые элементы в случае необходимости:
Java:
// Что-нибудь делаем и удаляем ники игроков, длина которых более 5 символов
List<String> list = new ArrayList<String>();
list.add("gamerforEA_MCPC");
list.add("Notch");
list.add("dinnerbone");
list.add("Player");

// Первый способ: простой, надёжный, красивый, но требуется Java 8
list.removeIf(string ->
{
    // Do something
    boolean needDelete = string.length() > 5;
    return needDelete;
});

// Второй способ: громоздкий, но работает даже на динозавре с Java 6
// Да, я видел, что в шапке сказано про Iterator, но всё же указал его, чтобы можно было провести аналогию с первым способом
Iterator<String> iterator = list.iterator();
while (iterator.hasNext())
{
    String string = iterator.next();
    // Do something
    boolean needDelete = string.length() > 5;
    if (needDelete)
        iterator.remove();
}
Более реальный пример (удаление из Map игроков, которых нет на сервере):
Java:
Map<String, UUID> cache = new HashMap<>();
// TODO Заполняем cache значениями
// Да, я знаю, что для этого примера лучше брать игрока по UUID из values(), но это лишь пример
cache.keySet().removeIf(playerName -> Bukkit.getOnlinePlayer(playerName) == null);
 
Последнее редактирование:
Назад
Верх