Избежание ConcurrentModificationException в списке путем создания мелкой копии - PullRequest
4 голосов
/ 24 августа 2011

У меня есть класс, подобный следующему:

class Test
{
    private LinkedList<Person> persons = new LinkedList<Person>;

    public synchronized void remove(Person person)
    {
        persons.remove(person);
    }

    public List<Person> getAllPersons()
    {
        // Clients may iterate over the copy returned and modify the structure.
        return new ArrayList<Person>(persons);
    }
}

persons может быть изменено одновременно: один - через remove() одним потоком, а два - через мелко скопированный экземпляр, возвращенный getAllPersons().

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

Почему в этом случае создание только мелкой копии persons позволяет избежать ConcurrentModificationException?

Ответы [ 2 ]

6 голосов
/ 24 августа 2011

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

В вашем коде все еще есть небольшая ошибка - для безопасного доступа к члену, который сам по себе не является потокобезопасным, вы должны synchronize в методе getAllPersons.

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

Обратите внимание, что этот не защищает вас от проблем безопасности потоков с вашим классом Person, а только сами коллекции. Если Person является неизменным, вы должны быть в порядке.

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

1 голос
/ 24 августа 2011

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

...