Еще один вопрос ConcurrentModificationException - PullRequest
1 голос
/ 05 ноября 2010

Я искал StackOverflow, и есть много вопросов об ConcurrentModificationException. Прочитав их, я все еще растерялся. Я получаю много этих исключений. Я использую настройку «Реестр» для отслеживания объектов:

public class Registry {
    public static ArrayList<Messages> messages = new ArrayList<Messages>();
    public static ArrayList<Effect> effects = new ArrayList<Effect>();
    public static ArrayList<Projectile> proj = new ArrayList<Projectile>();

    /** Clears all arrays */
    public static void recycle(){
        messages.clear();
        effects.clear();
        proj.clear();
    }
}

Я добавляю и удаляю объекты в эти списки, получая доступ к спискам ArrayLists следующим образом: Registry.effects.add(obj) и Registry.effects.remove(obj)

Мне удалось обойти некоторые ошибки с помощью цикла повторных попыток:

//somewhere in my game..
boolean retry = true;
while (retry){
    try {
        removeEffectsWithSource("CHARGE");
        retry = false;
    }
catch (ConcurrentModificationException c){}
}

private void removeEffectsWithSource(String src) throws ConcurrentModificationException {
    ListIterator<Effect> it = Registry.effects.listIterator();
    while ( it.hasNext() ){
        Effect f = it.next();
        if ( f.Source.equals(src) ) {
            f.unapplyEffects();
            Registry.effects.remove(f);
        }
    }
}

Но в других случаях это не практично. Я продолжаю получать ConcurrentModificationExceptions в моем методе drawProjectiles(), хотя он ничего не меняет. Я предполагаю, что виновником является то, что я коснулся экрана, который создает новый объект Projectile и добавляет его в Registry.proj, пока метод draw все еще итерируется.

Я не очень хорошо могу сделать цикл повторения с помощью метода draw, иначе он будет перерисовывать некоторые объекты. Так что теперь я вынужден найти новое решение ... Есть ли более стабильный способ выполнения того, что я делаю?

Да, и часть 2 моего вопроса: многие люди предлагают использовать ListIterators (как я использовал), но я не понимаю ... если я вызываю ListIterator.remove(), удаляет ли он этот объект из ArrayList, через который он перебирает, или просто удалить его из самого итератора?

Ответы [ 2 ]

2 голосов
/ 05 ноября 2010

Верхняя строка, три рекомендации:

  • Не делайте "обернуть исключение в цикле". Исключения составляют исключительные условия, а не контроль потока. ( Эффективная Java # 57 или Исключения и поток управления или Пример "использование исключений для потока управления" )
  • Если вы собираетесь использовать объект реестра, предоставьте потокобезопасные поведенческие , а не accessor методы для этого объекта и содержат аргументы параллелизма внутри этого единственного класса. Ваша жизнь станет лучше. Нет выставления коллекций в открытых полях . (э-э, а почему эти поля static?)
  • Чтобы решить реальные проблемы параллелизма, выполните одно из следующих действий:
    1. Использовать синхронизированные коллекции (потенциальное снижение производительности)
    2. Использовать одновременные коллекции (иногда сложная логика, но, вероятно, эффективная)
    3. Используйте снимки (вероятно, с synchronized или ReadWriteLock под крышками)

Часть 1 вашего вопроса

Вы должны использовать параллельную структуру данных для многопоточного сценария или использовать синхронизатор и делать защитную копию. Вероятно, неправильное указание коллекций как полей public неверно: ваш реестр должен предоставлять поточно-ориентированные поведенческие средства доступа к этим коллекциям. Например, может быть, вы хотите метод Registry.safeRemoveEffectBySource(String src). Сохраняйте особенности потоков в реестре, который, по-видимому, является «владельцем» этой совокупной информации в вашем дизайне.

Поскольку вам, вероятно, на самом деле не нужна семантика List, я предлагаю заменить ее на ConcurrentHashMaps, заключенную в Set, используя Collections.newSetFromMap().

Ваш метод draw() может либо: a) использовать метод Registry.getEffectsSnapshot(), который возвращает снимок набора; или б) использовать метод Iterable<Effect> Registry.getEffects(), который возвращает безопасную итерируемую версию (возможно, только при поддержке ConcurrentHashMap, которая не выдает CME ни при каких обстоятельствах). Я думаю, что (b) здесь предпочтительнее, пока цикл рисования не должен изменять коллекцию. Это обеспечивает очень слабую гарантию синхронизации между потоком (ами) мутатора и потоком draw(), но при условии, что поток draw() выполняется достаточно часто, пропуская обновление или что-то, вероятно, не имеет большого значения.

Часть 2 вашего вопроса

Как отмечается в другом ответе, в случае однопоточности вы должны просто убедиться, что вы используете Iterator.remove() для удаления элемента, но, опять же, вам следует заключить эту логику в класс Registry, если это возможно. В некоторых случаях вам нужно заблокировать коллекцию, выполнить итерацию по ней, собирая некоторую сводную информацию, и внести структурные изменения после ее завершения. Вы спрашиваете, просто ли метод remove() удаляет его из Iterator или из резервной коллекции ... см. Контракт API для Iterator.remove(), в котором говорится, что он удаляет объект из базовой коллекции. Также посмотрите этот ТАК * вопрос .

1 голос
/ 05 ноября 2010

Вы не можете напрямую удалить элемент из коллекции, пока вы еще итерируете по нему, в противном случае вы получите ConcurrentModificationException.

Решением является, как вы намекаете, вызов метода removeвместо этого итератор.Это также удалит его из базовой коллекции, но это будет сделано таким образом, чтобы итератор знал, что происходит, и поэтому не выбрасывал исключение, когда обнаруживал, что коллекция была изменена.

...