Подтвердить что ты не робот

Лучший способ контролировать одновременный доступ к коллекциям Java

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

Я не вижу свой вопрос в разделе "Вопросы, относящиеся к теме", и не в моем поиске (Сделать потоки потолочными? не то же самое).

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

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

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

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

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


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

Подводя итог:

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

Надеюсь, у меня все получилось...: -)

4b9b3361

Ответ 1

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

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

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

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

Ответ 2

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

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

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

Ответ 3

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

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

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

Ответ 4

я всегда хотел бы сначала перейти в пакет 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.

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

Ответ 5

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

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

Ответ 6

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

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

Ответ 7

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