Обнаружение параллельных модификаций?

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

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

Второе Обновление если кто-то хочет объединить упоминание synchronizedCollection и клонирования как Джейсон сделал с упоминанием java.утиль.параллельные и рамки коллекций apache, такие как jacekfoo и Javamann, я могу принять ответ.

12 ответов


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

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

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

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

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

вы также можете рассмотреть возможность просмотра Google collections framework.


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


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


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

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

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

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

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

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

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


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

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

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

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


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


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

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


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

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


посмотреть реализацию. Он в основном хранит int:

transient volatile int modCount;

и это увеличивается, когда есть "структурная Модификация" (например, удалить). Если итератор обнаруживает, что modCount изменен, он создает исключение параллельной модификации.

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

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


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

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

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

     }
   }

}

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

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


сборники.synchronizedList() отобразит список номинально потокобезопасный и java.утиль.одновременно имеет более мощные характеристики.


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

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);