java.util.ConcurrentModificationException для ArrayList - PullRequest
4 голосов
/ 28 февраля 2011

У меня есть класс Server и таймер внутри него, который предназначен для очистки мертвых клиентов (клиентов, которые потерпели крах). Я следовал приведенному ниже примеру, блокируя коллекцию, когда Timer перебирает пользователей, но я все еще получаю это исключение (после сбоя подключенного клиента).

http://www.javaperformancetuning.com/articles/fastfail2.shtml

List<User> users;
List<User> connectedUsers;
ConcurrentMap<User, IClient> clients;

...

users = Collections.synchronizedList(new ArrayList<User>());
connectedUsers = new ArrayList<User>();
clients = new ConcurrentHashMap<User, IClient>();
timer = new Timer();
timer.schedule(new ClearDeadClients(), 5000, 5000);

...

class ClearDeadClients extends TimerTask {
    public void run() {
        synchronized (users) {
            Iterator<User> it = users.iterator();
            while (it.hasNext()) {
                User user = it.next(); // Throws exception
                if (!connectedUsers.contains(user)) {
                    users.remove(user);
                    clients.remove(user);
                }
            }
        }       

        connectedUsers.clear();
    }
}

Ответы [ 2 ]

11 голосов
/ 28 февраля 2011

Вам нужно удалить из итератора не коллекцию.Это выглядело бы так:

Iterator<User> it = users.iterator();
while (it.hasNext()) {
    User user = it.next(); 
    if (!connectedUsers.contains(user)) {
         it.remove();
         clients.remove(user);
     }
}
9 голосов
/ 28 февраля 2011

Вы не можете изменить коллекцию во время итерации по ней - к сожалению, вы делаете это здесь с users, и в результате получается ConcurrentModificationException От собственных javadocs ArrayList :

Итераторы, возвращаемые методами iterator и listIterator этого класса, fail-fast : если список структурно изменен в любое время после создания итератора, любым способом, кроме как через итератор собственные remove или add методы, итератор выдаст ConcurrentModificationException. Таким образом, перед одновременной модификацией итератор быстро и чисто дает сбой, вместо того, чтобы рисковать произвольным недетерминированным поведением в неопределенное время в будущем.

Чтобы исправить эту конкретную ситуацию, вы можете вместо этого использовать собственный метод remove() Итератора, заменив эту строку:

users.remove(user);

с

it.remove();

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

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

  1. Возьмите копию коллекции (users в данном случае), итерируйте по копии и удалите элементы из оригинала .
  2. Во время итерации создайте набор элементов для удаления, а затем выполните массовое удаление после завершения итерации.
  3. Используйте реализацию List, которая может иметь дело с одновременными изменениями, например CopyOnWriteArrayList

Это довольно распространенный вопрос - см. Также (например) цикл в списке с вопросом о удалении для других ответов.

...