Обнаружение одновременных изменений? - PullRequest
8 голосов
/ 16 сентября 2008

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

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

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

Ответы [ 12 ]

6 голосов
/ 16 сентября 2008

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

4 голосов
/ 17 сентября 2008

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

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

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

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

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

Вы также можете рассмотреть Каркас коллекций Google .

3 голосов
/ 16 сентября 2008

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

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

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

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

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

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

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

3 голосов
/ 16 сентября 2008

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

2 голосов
/ 16 сентября 2008

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

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

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

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

1 голос
/ 28 апреля 2009

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

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

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

     }
   }

}

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

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

1 голос
/ 16 сентября 2008

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

transient volatile int modCount;

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

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

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

1 голос
/ 16 сентября 2008

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

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

1 голос
/ 16 сентября 2008

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

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

1 голос
/ 16 сентября 2008

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

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