2008-09-16 5 views
7

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

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

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

+0

RE ОБНОВЛЕНИЕ: Сочетание использования синхронизированного сбора и дублировании содержимого списка перед тем итерация необходима. – 2008-09-16 18:08:55

+0

Джейсон, да, я вижу это сейчас. Если бы вы добавили упоминание о CopyOnWriteArrayList в свой ответ, я бы принял его. – 2008-09-16 18:20:31

+0

Важно ли, что при повторном запуске «живая» версия списка не изменяется или вы можете перебирать возможно устаревшую копию? Я думаю, что может возникнуть проблема с дизайном (кто контролирует доступ к списку?), А также использование синхронизации и java.util.concurrent. – 2008-09-16 19:34:13

ответ

4

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

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

Джейсон дает a specific way to achieve thread safety, and to avoid throwing a ConcurrentModificationException, но только за счет живучести.

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

Если вы уже используете некоторые библиотеки Apache Commons, тогда как jacekfoo points out, apache collections framework содержит полезные классы.

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

3

Отъезд java.util.concurrent для версий стандартного класса Коллекции, которые разработаны для лучшей обработки параллелизма.

1

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

3

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

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

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

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

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

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

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

2

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

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

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

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

1

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

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

1

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

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

6

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

1

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

transient volatile int modCount; 

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

Синхронизации (через Collections.synchronizedXXX) не будет делать хорошо, так как это не гарантирует безопасность итератора синхронизирует только записи и чтения с помощью пут, получить, установить ...

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

-1

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

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

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

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

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

    } 
    } 

} 

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

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

Смежные вопросы