2009-02-18 4 views
24

Должен ли я использовать старую синхронизированную коллекцию Vector, ArrayList с синхронизированным доступом или Collections.synchronizedList или какое-либо другое решение для одновременного доступа?Лучший способ контролировать параллельный доступ к коллекциям Java

Я не вижу свой вопрос во взаимосвязанных вопросах и в моем поиске (Make your collections thread-safe? - это не то же самое).

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

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

В любом случае, не очень знакомый с проблемами параллелизма, я немного искал информацию и задавался вопросом, что выбрать между старым (и, возможно, устаревшим) вектором (который имеет синхронизированные операции по дизайну), ArrayList с synchronized (myList) { } вокруг критического (добавлять/удалять/перемещать операции) или использовать список, возвращенный Collections.synchronizedList (даже не уверен, как использовать последний).

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

Но каковы плюсы и минусы других подходов?


[EDIT] Много хороших советов здесь, трудно выбрать. Я выберу более подробные и обеспечивающие ссылки/пищу для размышлений ... :-) Мне нравится Даррон.

Резюмируя:

  • Как я и подозревал, Вектор (и его злой двойник, Hashtable, а также, вероятно) в значительной степени устарели, я видел людей, говорил его старый дизайн не так хорошо, как новые коллекции ', за медленностью синхронизации, вызванной даже в среде с одним потоком. Если мы сохраним это, это происходит главным образом из-за того, что старые библиотеки (и части Java API) все еще используют его.
  • В отличие от того, что я думал, Collections.synchronizedXxxx не являются более современными, чем Vector (они, похоже, современны для Collections, то есть Java 1.2!), А не лучше, на самом деле. Хорошо знать. Короче говоря, я должен их избегать.
  • Ручная синхронизация, похоже, является хорошим решением в конце концов. Могут быть проблемы с производительностью, но в моем случае это не критично: операции, выполняемые с действиями пользователя, небольшая коллекция, не частое использование.
  • java.util.concurrent пакет стоит иметь в виду, особенно методы CopyOnWrite.

Я надеюсь, что я получил это право ... :-)

+0

Также см. Http://stackoverflow.com/questions/510632/whats-the-difference-between-concurrenthashmap-and-collections-synchronizedmapm – eljenso

ответ

23

Вектор и список, возвращенные Collections.synchronizedList() - это морально то же самое. Я считаю, что Vector эффективно (но не на самом деле) устарел и всегда предпочитает синхронизированный список. Единственным исключением были бы старые API (особенно в JDK), которые требуют Vector.

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

Еще один вариант, который вы, возможно, захотите рассмотреть, - это CopyOnWriteArrayList, который даст вам безопасность потоков, как в Vector, так и в синхронизированном ArrayList, но также итераторы, которые не будут бросать ConcurrentModificationException, поскольку они работают с неактивным снимком данных.

Вы можете найти некоторые из этих последних блогов по этим темам интересным:

3

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

Я считаю, что для обеспечения безотказной работы итерации вам придется самостоятельно выполнять синхронизацию. Предполагая, что один и тот же вектор/список/объект разделяется несколькими потоками (и, следовательно, приводит к вашим проблемам), не можете ли вы просто синхронизировать этот объект?

10

Я не могу придумать, почему я предпочитаю Vector над ArrayList. Операции с списком в Vector синхронизированы, что означает, что несколько потоков могут безопасно его изменять. И, как вы говорите, операции ArrayList могут быть синхронизированы с использованием Collections.synchronizedList.

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

Общей методикой, используемой для предотвращения итерационной проблемы, является перебор по неизменяемым копиям коллекции. См. Collections.unmodifiableList

7

Я бы всегда пошел в пакет java.util.concurrent (http://java.sun.com/javase/6/docs/api/java/util/concurrent/package-summary.html) и посмотрел, есть ли подходящая коллекция. класс java.util.concurrent.CopyOnWriteArrayList хорош, если вы делаете несколько изменений в списке, но больше итераций.

также я не верю, что Vector и Collections.synchronizedList предотвратит ConcurrentModificationException.

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

2

Безопасное решение заключается в том, чтобы избежать одновременного доступа к общим данным в целом. Вместо того, чтобы потоки, отличные от EDT, работают с одними и теми же данными, попросите их позвонить SwingUtilities.invokeLater() с Runnable, который выполняет изменения.

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

2

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

13

я настоятельно рекомендую книгу "Java Concurrency in Practice".

Каждый из вариантов имеет преимущества/недостатки:

  1. Вектор - считается "устаревшим". Он может получить меньше внимания и исправлять ошибки, чем больше коллекций.
  2. Ваши собственные блоки синхронизации - очень легко получить неправильную информацию. Часто дает более низкую производительность, чем ниже.
  3. Collections.synchronizedList() - выбор 2, сделанный экспертами. Это все еще не завершено из-за многоступенчатых операций, которые должны быть атомарными (get/modify/set or iteration).
  4. Новые классы от java.util.concurrent - часто имеют более эффективные алгоритмы, чем выбор 3. Аналогичные предостережения о многоступенчатых операциях применяются, но инструменты, которые вам помогут, часто предоставляются.
Смежные вопросы