Вопрос

В многопоточном приложении, над которым я работаю, мы иногда видим ConcurrentModificationExceptions в наших списках (которые в основном ArrayList, иногда векторы).Но бывают и другие случаи, когда я думаю, что одновременные изменения происходят, потому что при итерации по коллекции кажется, что отсутствуют элементы, но никаких исключений не возникает.Я знаю, что документы для ConcurrentModificationException говорит, что на него нельзя положиться, но как мне убедиться, что я одновременно не изменяю список?И является ли заключение каждого доступа к коллекции в синхронизированный блок единственным способом предотвратить это?

Обновлять: Да, я знаю о Collections.synchronizedCollection, но он не защищает от изменения кем-либо коллекции во время ее итерации.Я думаю, что по крайней мере часть моих проблем возникает, когда кто-то добавляет что-то в коллекцию, пока я ее просматриваю.

Второе обновление Если кто-то хочет объединить упоминание синхронизированной коллекции и клонирования, как это сделал Джейсон, с упоминанием java.util.concurrent и фреймворков коллекций Apache, таких как jacekfoo и Javamann, я могу принять ответ.

Это было полезно?

Решение

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

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

Джейсон дает особый способ обеспечить безопасность потоков и избежать создания исключения ConcurrentModificationException., но только в ущерб живости.

Джаваманн упоминает два конкретных класса в java.util.concurrent пакет, который решает ту же проблему без блокировки, где масштабируемость имеет решающее значение.Они поставлялись только с Java 5, но существовали различные проекты, которые переносили функциональность пакета в более ранние версии Java, в том числе Вот этот, хотя в более ранних JRE они не будут иметь такой хорошей производительности.

Если вы уже используете некоторые библиотеки Apache Commons, то как jacekfoo указывает на то, платформа коллекций Apache содержит несколько полезных классов.

Вы также можете рассмотреть возможность просмотра Платформа коллекций Google.

Другие советы

В зависимости от частоты обновления одним из моих любимых является CopyOnWriteArrayList или CopyOnWriteArraySet.Они создают новый список/набор обновлений, чтобы избежать исключений одновременного изменения.

Проверить java.util.concurrent для версий стандартных классов Collections, которые разработаны для лучшей обработки параллелизма.

Да, вам необходимо синхронизировать доступ к объектам коллекций.

Альтернативно вы можете использовать синхронизированные оболочки вокруг любого существующего объекта.Видеть Коллекции.synchronizedCollection().Например:

List<String> safeList = Collections.synchronizedList( originalList );

Однако весь код должен использовать безопасную версию, и даже в этом случае повторение при изменении другого потока приведет к проблемам.

Чтобы решить проблему итерации, сначала скопируйте список.Пример:

for ( String el : safeList.clone() )
{ ... }

Для более оптимизированных, потокобезопасных коллекций также посмотрите java.util.concurrent.

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

Самый простой способ проверить это:

List<Blah> list = new ArrayList<Blah>();
for (Blah blah : list) {
     list.remove(blah); // will throw the exception
}

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

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

Правильный способ сделать это — обернуть доступ к коллекции в синхронизированный блок.Стандартная практика программирования требует использования какого-либо механизма блокировки (семафора, мьютекса и т. д.) при работе с состоянием, которое используется несколькими потоками.

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

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

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

Посмотрите реализацию.В основном он хранит int:

transient volatile int modCount;

и оно увеличивается при «структурной модификации» (например, при удалении).Если итератор обнаруживает, что modCount изменился, он выдает исключение одновременной модификации.

Синхронизация (через Collections.synchronizedXXX) не принесет пользы, поскольку она не гарантирует безопасность итератора, а синхронизирует только запись и чтение через put, get, set...

См. java.util.concurennt и структуру коллекций Apache (в ней есть некоторые оптимизированные классы, которые работают правильно в параллельной среде, когда операций чтения (несинхронизированных) больше, чем записей - см. FastHashMap.

Вы также можете синхронизировать итерации по списку.

List<String> safeList = Collections.synchronizedList( originalList );

public void doSomething() {
   synchronized(safeList){
     for(String s : safeList){
           System.out.println(s);

     }
   }

}

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

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

Коллекции.synchronizedList() номинально отобразит список как потокобезопасный, а java.util.concurrent имеет более мощные функции.

Это избавит вас от исключения одновременной модификации.Про эффективность, правда, говорить не буду ;)

List<Blah> list = fillMyList();
List<Blah> temp = new ArrayList<Blah>();
for (Blah blah : list) {
     //list.remove(blah);  would throw the exception
     temp.add(blah);
}
list.removeAll(temp);
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top