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

Различные типы потокобезопасных наборов в Java

Кажется, есть много разных реализаций и способов генерирования поточно-безопасных наборов в Java. Некоторые примеры включают

1) CopyOnWriteArraySet

2) Collections.synchronizedSet(Set set)

3) ConcurrentSkipListSet

4) Collections.newSetFromMap (новый ConcurrentHashMap())

5) Другие множества, сгенерированные способом, аналогичным (4)

Эти примеры взяты из шаблона параллелизма: реализации параллельного набора в Java 6

Может ли кто-нибудь просто объяснить различия, преимущества и недостатки этих и других примеров? У меня возникли проблемы с пониманием и сохранностью всего, что связано с Java Std Docs.

4b9b3361

Ответ 1

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

Используйте это только для действительно маленьких наборов, которые будут часто читаться (повторяться) и редко меняться. (Swings listener-sets будет примером, но они на самом деле не наборы, и в любом случае их следует использовать только от EDT.)

2) Collections.synchronizedSet будет просто обертывать синхронизированный блок вокруг каждого метода исходного набора. Вы не должны напрямую обращаться к исходному набору. Это означает, что никакие два метода набора не могут выполняться одновременно (один будет блокироваться до тех пор, пока не закончится другой) - это поточно-безопасный, но у вас не будет concurrency, если несколько потоков действительно используют набор. Если вы используете итератор, вам, как правило, по-прежнему необходимо синхронизировать внешнее, чтобы избежать ConcurrentModificationExceptions при изменении набора между вызовами итератора. Производительность будет похожа на производительность исходного набора (но с некоторыми издержками синхронизации и блокировкой при одновременном использовании).

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

3) ConcurrentSkipListSet - это параллельная реализация SortedSet, с большинством основных операций в O (log n). Он позволяет одновременное добавление/удаление и чтение/итерацию, где итерация может или не может сообщать об изменениях с момента создания итератора. Объемные операции - это просто несколько одиночных вызовов, а не атомарно - другие потоки могут наблюдать только некоторые из них.

Очевидно, вы можете использовать это, только если у вас есть определенный порядок на ваших элементах. Это выглядит как идеальный кандидат для ситуаций с высоким уровнем concurrency для не слишком больших множеств (из-за O (log n)).

4) Для ConcurrentHashMap (и набора, полученного из него): Здесь наиболее основные параметры (в среднем, если у вас есть хороший и быстрый hashCode()) в O (1) (но могут выродиться до O (n)), как и для HashMap/HashSet. Для записи существует ограниченный concurrency (таблица разделена на разделы, а доступ на запись будет синхронизирован на нужном разделе), в то время как доступ на чтение полностью параллелен самому себе и потокам записи (но может еще не увидеть результаты изменений в настоящее время написано). Итератор может или не может видеть изменения с момента его создания, а массовые операции не являются атомарными. Масштабирование происходит медленно (как для HashMap/HashSet), поэтому старайтесь избегать этого, оценивая необходимый размер при создании (и используя примерно 1/3 от этого, поскольку он изменяет размер при заполнении 3/4).

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

5) Существуют ли другие параллельные реализации карт, которые можно использовать здесь?

Ответ 2

Можно объединить производительность contains() HashSet с concurrency -связанными свойствами CopyOnWriteArraySet с помощью AtomicReference<Set> и заменить весь набор на каждую модификацию.

Эскиз реализации:

public abstract class CopyOnWriteSet<E> implements Set<E> {

    private final AtomicReference<Set<E>> ref;

    protected CopyOnWriteSet( Collection<? extends E> c ) {
        ref = new AtomicReference<Set<E>>( new HashSet<E>( c ) );
    }

    @Override
    public boolean contains( Object o ) {
        return ref.get().contains( o );
    }

    @Override
    public boolean add( E e ) {
        while ( true ) {
            Set<E> current = ref.get();
            if ( current.contains( e ) ) {
                return false;
            }
            Set<E> modified = new HashSet<E>( current );
            modified.add( e );
            if ( ref.compareAndSet( current, modified ) ) {
                return true;
            }
        }
    }

    @Override
    public boolean remove( Object o ) {
        while ( true ) {
            Set<E> current = ref.get();
            if ( !current.contains( o ) ) {
                return false;
            }
            Set<E> modified = new HashSet<E>( current );
            modified.remove( o );
            if ( ref.compareAndSet( current, modified ) ) {
                return true;
            }
        }
    }

}

Ответ 3

Если Javadocs не помогают, вы, вероятно, должны просто найти книгу или статью для чтения о структурах данных. С первого взгляда:

  • CopyOnWriteArraySet создает новую копию базового массива каждый раз, когда вы мутируете коллекцию, поэтому записи медленны, и итераторы бывают быстрыми и последовательными.
  • Collections.synchronizedSet() использует вызовы с синхронизированными вызовами старой школы, чтобы сделать Set threadsafe. Это будет невысокая версия.
  • ConcurrentSkipListSet предлагает исполняемые записи с непоследовательными пакетными операциями (addAll, removeAll и т.д.) и итераторами.
  • Collections.newSetFromMap(новый ConcurrentHashMap()) имеет семантику ConcurrentHashMap, которая, как я полагаю, не обязательно оптимизирована для чтения или записи, но, как и ConcurrentSkipListSet, имеет непоследовательные пакетные операции.

Ответ 4

Параллельный набор слабых ссылок

Другой поворот - это потокобезопасный набор слабых ссылок.

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

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

Сначала мы начнем с создания Set слабых ссылок, используя класс WeakHashMap. Это показано в документации класса для Collections.newSetFromMap.

Set< YourClassGoesHere > weakHashSet = 
    Collections
    .newSetFromMap(
        new WeakHashMap< YourClassGoesHere , Boolean >()
    )
;

Значение карты, Boolean, здесь не имеет значения, так как ключ карты составляет наш Set.

В таком сценарии, как pub-sub, нам нужна безопасность потоков, если подписчики и издатели работают в разных потоках (вполне вероятно, что так и есть).

Сделайте еще один шаг, обернув его как синхронизированный набор, чтобы сделать этот набор потокобезопасным. Поток в вызов Collections.synchronizedSet.

this.subscribers =
        Collections.synchronizedSet(
                Collections.newSetFromMap(
                        new WeakHashMap <>()  // Parameterized types '< YourClassGoesHere , Boolean >' are inferred, no need to specify.
                )
        );

Теперь мы можем добавлять и удалять подписчиков из нашего результирующего Set. И любые "исчезающие" подписчики в конечном итоге будут автоматически удалены после выполнения сборки мусора. Когда это произойдет, зависит от вашей реализации сборщика мусора в JVM и зависит от ситуации времени выполнения. Для обсуждения и примера того, когда и как базовый WeakHashMap очищает записи с истекшим сроком, см. Этот вопрос: * Является ли WeakHashMap постоянно растущим или он очищает ключи мусора? *