Почему я получаю исключение java.util.ConcurrentModificationException с итератором? - PullRequest
1 голос
/ 04 ноября 2011

Я пытаюсь написать игру, поэтому каждый кадр, в котором я вызываю метод doDraw (), где я использую итератор, чтобы перебрать все объекты GameObject и вывести их на экран:

Iterator<GameObject> itr = mObjList.iterator();
    while (itr.hasNext()) {
        GameObject obj = itr.next(); // this line gives me the error
        ...
        // print object
    }

Единственный метод, который добавляет элемент в список, это:

public void click(int x, int y) {
    // adds new object to the list on a click event
    mObjList.add(new GameObject(x, y));
}

В большинстве случаев это работает. Но иногда я получаю эту ошибку:

java.util.ConcurrentModificationException

Из строки с "itr.next ()". Из того, что я гуглил, я подумал, что это потому, что событие click () иногда происходит до того, как draw () заканчивает рисовать каждый объект, поэтому он меняет список, пока его использует итератор. Я полагаю, что это не так?

Но я не разбираюсь с темами. Как я мог это исправить? Может быть, я делаю все это неправильно, и я должен использовать совершенно другой метод для печати всех объектов на экране?

Ответы [ 5 ]

4 голосов
/ 04 ноября 2011

Если ожидаемое количество операций чтения и обхода значительно превышает число обновлений в списке, используйте CopyOnWriteArrayList .

В противном случае, synchronize в списке (или выделенном объекте мьютекса) при итерации и мутировании:

private List<GameObject> mObjList = /* whatever */;
private final Object mListMutex = new Object();

// snip...

synchronized (mListMutex) {
    for (GameObject obj : mObjList) {
        // do your thang
    }
}

// snip...
public void click(int x, int y) {
    GameObject obj = new GameObject(x, y);
    synchronized (mListMutex) {
        mObjList.add(obj);
    }
}
2 голосов
/ 04 ноября 2011

Я бы хотел добавить к ответу @aleph_null. @aleph_null правильно, что это исключение происходит, когда вы пытаетесь изменить коллекцию во время итерации по ней - разрешен только метод remove() на итераторе. Итератор пытается защитить себя от изменений, происходящих с коллекцией под ним.

Однако я бы не рекомендовал синхронизацию как правильное решение. Если вам нужно добавить что-то в список во время его обработки, я рекомендую добавить его в другой список и затем вызвать addAll(), как только вы прекратите итерацию. Конечно, интенсивнее GC, но чище.

Edit:

Извините, я упустил тот факт, что click() - это асинхронное событие, передаваемое другим потоком. Я предположил, что click() был вызван внутри цикла. При добавлении в click() и вокруг addAll() вам нужно будет синхронизировать список. Вы можете использовать AtomicReference, чтобы записать щелчок, а затем действовать по нему после завершения итератора, но только в том случае, если вы гарантировали, что одновременно нажимается только один элемент.

0 голосов
/ 04 ноября 2011

Ваше предположение о том, почему вы получаете ConcurrentModificationException, верно.

Чтобы исправить это, вы можете выполнить синхронизацию в самом списке:

synchronized(mObjList) {
    Iterator itr = mObjList.iterator();
    while (itr.hasNext()) {
        // ...
    }
}

Это позволит вашему потоку получить блокировку для самого списка. Если вы перенесете свой доступ к списку из другого потока аналогичным образом, это не позволит вашим потокам наступать друг на друга (см. Внутренние блокировки ).

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

0 голосов
/ 04 ноября 2011

noooo, ошибки синхронизации неприятны ... тем более что они кажутся случайными.Обходя коллекцию с помощью итератора, вы не можете изменять коллекцию (если вы не используете метод удаления итератора, но это исключение).Это приводит к исключению, на которое вы смотрите ... но только иногда.Ошибка произойдет, только когда mObjList.add будет вызван правильно, когда вы пересекаете итератор.

0 голосов
/ 04 ноября 2011

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...