Лучший способ управления одновременным доступом к коллекциям Java - PullRequest
25 голосов
/ 18 февраля 2009

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

Я не вижу своего вопроса ни в связанных вопросах, ни в моем поиске ( Сделать ваши коллекции безопасными для потоков? не совпадает).

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

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

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

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

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


[РЕДАКТИРОВАТЬ] Здесь много хороших советов, трудно выбрать один. Я выберу более подробный и предоставляющий ссылки / пищу для размышлений ... :-) Мне тоже нравится Даррон.

Подведем итог:

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

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

Ответы [ 7 ]

23 голосов
/ 19 февраля 2009

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

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

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

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

13 голосов
/ 19 февраля 2009

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

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

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

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

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

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

7 голосов
/ 18 февраля 2009

Сначала я всегда обращался к пакету 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.

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

3 голосов
/ 18 февраля 2009

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

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

2 голосов
/ 19 февраля 2009

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

2 голосов
/ 18 февраля 2009

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

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

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