1) Это CopyOnWriteArraySet
довольно простая реализация - она в основном имеет список элементов в массиве и при изменении списка копирует массив. Итерации и другие обращения, которые выполняются в это время, продолжаются со старым массивом, избегая необходимости синхронизации между читателями и пишущими (хотя сама запись должна быть синхронизирована). Обычно операции быстрого набора (особенно contains()
) довольно медленные, так как массивы будут искать в линейном времени.
Используйте это только для действительно небольших наборов, которые будут часто читаться (повторяться) и изменяться редко. (Наборы слушателей Swings были бы примером, но на самом деле это не наборы, и в любом случае их следует использовать только из EDT.)
2) Collections.synchronizedSet
просто обернет синхронизированный блок вокруг каждого метода исходного набора. Вы не должны получать доступ к оригинальному набору напрямую. Это означает, что никакие два метода набора не могут выполняться одновременно (один будет блокироваться, пока другой не завершится) - это потокобезопасно, но у вас не будет параллелизма, если множество потоков действительно использует набор. Если вы используете итератор, вам, как правило, по-прежнему необходимо выполнять внешнюю синхронизацию, чтобы избежать исключений ConcurrentModificationExceptions при изменении набора между вызовами итератора. Производительность будет аналогична производительности исходного набора (но с некоторыми накладными расходами синхронизации и блокировкой, если используется одновременно).
Используйте это, если у вас только низкий уровень параллелизма и вы хотите, чтобы все изменения были немедленно видны другим потокам.
3) ConcurrentSkipListSet
является параллельной SortedSet
реализацией, с большинством основных операций в O (log n). Это позволяет одновременное добавление / удаление и чтение / итерацию, когда итерация может или не может рассказать об изменениях с момента создания итератора. Массовые операции - это просто множественные одиночные вызовы, а не атомарно - другие потоки могут наблюдать только некоторые из них.
Очевидно, что вы можете использовать это, только если у вас есть общий порядок ваших элементов. Это выглядит как идеальный кандидат для ситуаций с высоким параллелизмом, для не слишком больших множеств (из-за O (log n)).
4) Для ConcurrentHashMap
(и набора, полученного из него): здесь большинство основных опций (в среднем, если у вас хорошо и быстро hashCode()
) в O (1) (но может выродиться в O (n)), как для HashMap / HashSet. Существует ограниченный параллелизм для записи (таблица разбита на разделы, и доступ на запись будет синхронизирован на нужном разделе), в то время как доступ на чтение полностью параллелен для самого себя и потоков записи (но может еще не увидеть результаты изменений, которые в данный момент находятся написано). Итератор может видеть или не видеть изменения, так как он был создан, и массовые операции не являются атомарными. Изменение размера происходит медленно (как для HashMap / HashSet), поэтому постарайтесь избежать этого, оценивая необходимый размер при создании (и используя примерно 1/3 от этого, поскольку он изменяет размер при заполнении 3/4).
Используйте это, когда у вас большие наборы, хорошая (и быстрая) хеш-функция, и вы можете оценить размер набора и необходимый параллелизм перед созданием карты.
5) Есть ли другие параллельные реализации карт, которые можно использовать здесь?
ConcurrentHashMap
основе набора «таким образом постарайтесь избежать этого путем оценки необходимого размера при создании». Размер, который вы задаете карте, должен быть более чем на 33% больше, чем ваша оценка (или известное значение), так как размер набора изменяется при загрузке 75%. Я используюexpectedSize + 4 / 3 + 1
+
должен быть*
?expectedSize * 4 / 3 + 1
ConcurrentMap
(илиHashMap
) в Java 8, если число записей, отображаемых в одно и то же ведро, достигает порогового значения (я полагаю, это 16), тогда список изменяется на двоичное дерево поиска (красно-черное дерево должно быть уточнено), и в этом случае ищите время было быO(lg n)
и нетO(n)
.Можно комбинировать
contains()
производительностьHashSet
со свойствами, связанными с параллелизмомCopyOnWriteArraySet
, используяAtomicReference<Set>
и заменяя весь набор в каждой модификации.Эскиз реализации:
источник
AtomicReference
отмечает значение volatile. Это означает, что он гарантирует, что ни один поток не считывает устаревшие данные, и предоставляетhappens-before
гарантию, поскольку компилятор не может переупорядочивать код. Но если используются только методы get / setAtomicReference
, то мы на самом деле причудливо помечаем нашу переменную volatile.abstract
, чтобы избежать необходимости написания нескольких методов. Я начал добавлять их, но столкнулся с препятствиемiterator()
. Я не знаю, как поддерживать итератор над этим, не нарушая модель. Кажется, мне всегда приходится проходить черезref
и каждый раз получать новый базовый набор, что требует нового итератора для базового набора, что для меня бесполезно, так как он начинается с нулевого элемента. Есть идеи?Если Javadocs не помогают, вы, вероятно, должны просто найти книгу или статью, чтобы прочитать о структурах данных. С одного взгляда:
источник
Параллельный набор слабых ссылок
Другой поворот - это потокобезопасный набор слабых ссылок .
Такой набор удобен для отслеживания подписчиков в сценарии pub-sub . Когда подписчик выходит из области действия в других местах и, следовательно, стремится стать кандидатом на сборку мусора, подписчику не нужно беспокоиться о изящной отписке. Слабая ссылка позволяет подписчику завершить переход к кандидату на сборку мусора. Когда мусор в конечном итоге собирается, запись в наборе удаляется.
Хотя в комплекте классов нет такого набора, вы можете создать его с помощью нескольких вызовов.
Сначала мы начнем со
Set
слабых ссылок, используяWeakHashMap
класс. Это показано в документации класса дляCollections.newSetFromMap
.Значение карты,
Boolean
является здесь неуместно как ключ карты составляет нашиSet
.В таком сценарии, как pub-sub, нам нужна безопасность потоков, если подписчики и издатели работают в отдельных потоках (вполне вероятно, что так и будет).
Сделайте еще один шаг, обернув его как синхронизированный набор, чтобы сделать этот набор потокобезопасным. Поток в призыв к
Collections.synchronizedSet
.Теперь мы можем добавлять и удалять подписчиков из нашего результата
Set
. И любые «исчезающие» подписчики в конечном итоге будут автоматически удалены после выполнения сборки мусора. Когда это выполнение произойдет, зависит от реализации сборщика мусора в вашей JVM и зависит от ситуации времени выполнения в данный момент. Обсуждение и пример того, когда и как базовый инструментWeakHashMap
очищает просроченные записи, см. В этом Вопросе: * Является ли WeakHashMap постоянно растущим или он очищает ключи мусора? * .источник