1. Этот сайт использует файлы cookie. Продолжая пользоваться данным сайтом, Вы соглашаетесь на использование нами Ваших файлов cookie. Узнать больше.
  2. Вы находитесь в сообществе Rubukkit. Мы - администраторы серверов Minecraft, разрабатываем собственные плагины и переводим на различные языки плагины наших коллег из других стран.
    Скрыть объявление
Скрыть объявление
В преддверии глобального обновления, мы проводим исследования, которые помогут нам сделать опыт пользования форумом ещё удобнее. Помогите нам, примите участие!

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

Тема в разделе "Разработка плагинов для новичков", создана пользователем molor, 19 фев 2017.

  1. Автор темы
    molor

    molor Активный участник Пользователь

    Баллы:
    66
    Привет.

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

    Код:
    for (String string : list.keySet()) {
    // do Something..
    }
    При соблюдении некоторых условий, внутри этого for происходит вызов функции function(тот самый string из for);, внутри которой завершающим этапом следует такой код:

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

    Я знаю про Iterator, но использовать его не могу, так как функция function(string);, в которой выполняется удаление из list, вызывается не только в вышепоказанном цикле, но и в одиночку на любом из элементов массива list по желанию игрока - а там listIterator.remove(); не прокатит.
     
  2. Автор темы
    molor

    molor Активный участник Пользователь

    Баллы:
    66
    Не знаю, зачем @alexandrage удалил три своих сообщения, но он натолкнул меня на мысль использовать ConcurrentHashMap вместо HashMap. Вроде бы помогло, но тему пока оставлю открытой. Вдруг я ошибся.
     
  3. AtomicInteger

    AtomicInteger Активный участник Пользователь

    Баллы:
    76
    Когда ты создаешь foreach-цикл, создается неявный итератор, который используется для прохода по List.Когда ты одновременно используешь итератор(который имеет фиксированные параметры на счёт листа, который он итерирует, thread-safe) и любой другой способ изменения листа, не относящийся к итератору, то возникает ConcurrentModificationException.Советую создать другой метод(function), который бы мог работать с итератором.
    ConcurrentHashMap в этом случае не совсем верный вариант.Такой вид коллекции используется в мультипоточных средах, к тому же его использования лучше избегать, так как он медленнее.Почти все коллекции в Java потоконебезопасны, потому что в случае с synchronized коллекция обрабатывается поочерёдно, а значит медленно.В твоем же случае, как я выше написал, лист обрабатывается неявным итератором и методом непосредственно из самого List, отсюда и конкуренция.Например: Ты создал цикл foreach, он создал неявный итератор, итератор понял, что в листе есть n элементов, за время итерации удалился 1 элемент, теперь у нас размер листа n-1, а итератор всё ещё думает что n, тогда и выбрасывается такая ошибка.
     
  4. Blc_Dragon

    Blc_Dragon Активный участник Пользователь

    Баллы:
    76
    хмм, странно, я в таких случаях просто outOfBounds ловил
     
  5. AtomicInteger

    AtomicInteger Активный участник Пользователь

    Баллы:
    76
    Как вариант, ты можешь использовать обычный цикл for, так как неявный итератор не создается, но тогда возможен ArrayOutOfBoundsException.
    Как второй вариант, можешь создать репликацию листа, создать foreach на нём(и соотв. проходить по нему) а изменять первый лист, но сам понимаешь, этот вариант "не совсем" хорош.
    Зависит от ситуации, возможно, ты проходил по entrySet'y, получая значение и проводя изменения в массиве используя значение как ключ.Например, ты проходишь по массиву, получаешь значение 5, а потом пытаешься получить элемент по индексу 5 или по ключу 5, а там такого не оказывается, тогда и вылетает ArrayOutOfBoundsException.
     
  6. Blc_Dragon

    Blc_Dragon Активный участник Пользователь

    Баллы:
    76
    да, да я там просто форку запускал по размеру. сорре :oops:
     
  7. 0x1EE7C0DE

    0x1EE7C0DE Участник Пользователь

    Баллы:
    36
    Я решил эту проблему так
    Код:
    for(String str : ImmutableList.copyOf(list.keySet()))
    P.S. ImmutableList - класс из библиотеки Guava, которая уже включена в ядро, так что придётся подключить его вместо обычного API баккита
     
  8. xDark

    xDark Активный участник Пользователь

    Баллы:
    96
    HashSet<String> toRemove = new HashSet<>();
    for (String s : ...) {
    if (...)
    toRemove.add(s);
    }
    toRemove.forEach(s -> { ...remove(s); });
    toRemove.clear();
     
  9. 0x1EE7C0DE

    0x1EE7C0DE Участник Пользователь

    Баллы:
    36
    Ну если так, то для этого есть метод специальный
    Код:
    targetCollection.removeAll(toRemove);
     
  10. Типа админ:D

    Типа админ:D Активный участник Пользователь

    Баллы:
    76
    Имя в Minecraft:
    Qamulex
    Я вообще пользуюсь говнокодом.
    Делаю копию списка(s2). Нормальный список(s1)
    Так вот, копию я такой сделал, перебираю s2 в цикле, а в s1 убираю...........
     
  11. gamerforEA

    gamerforEA Старожил Пользователь

    Баллы:
    143
    Skype:
    sk2000sk1
    Имя в Minecraft:
    gamerforEA_MCPC
    Два быстрых способа проитерировать коллекцию и удалить некоторые элементы в случае необходимости:
    Код:
    // Что-нибудь делаем и удаляем ники игроков, длина которых более 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 игроков, которых нет на сервере):
    Код:
    Map<String, UUID> cache = new HashMap<>();
    // TODO Заполняем cache значениями
    // Да, я знаю, что для этого примера лучше брать игрока по UUID из values(), но это лишь пример
    cache.keySet().removeIf(playerName -> Bukkit.getOnlinePlayer(playerName) == null);
     
    Последнее редактирование: 20 фев 2017

Поделиться этой страницей