Потокобезопасная итерация по коллекции - PullRequest
19 голосов
/ 23 декабря 2010

Мы все знаем, что при использовании Collections.synchronizedXXX (например, synchronizedSet()) мы получаем синхронизированное «представление» базовой коллекции.

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

Какой вариант вы выберете для решения этой проблемы?

Я вижу только следующие подходы:

  1. Сделайте так, как указано в документации: синхронизировать в коллекции
  2. Клонировать коллекцию перед вызовом iterator()
  3. Использовать коллекцию, итератор которой является поточно-ориентированным (мне известно только о CopyOnWriteArrayList / Set)

И в качестве дополнительного вопроса: при использовании синхронизированного представления безопасно ли использование foreach / Iterable для потока?

Ответы [ 8 ]

26 голосов
/ 23 декабря 2010

Вы уже ответили на свой бонусный вопрос действительно: нет, использование расширенного цикла for небезопасно - потому что он использует итератор.

Что касается того, который является наиболее подходящим подходом - это действительно зависит от того, как ваш контекст:

  • Являются ли записи очень редкими? Если это так, CopyOnWriteArrayList может быть наиболее подходящим.
  • Является ли коллекция достаточно маленькой, а итерация быстрой? (т. е. вы не выполняете много работы в цикле). Если это так, синхронизация вполне может подойти - особенно если это происходит не слишком часто (т. е. у вас не будет большого количества разногласий по поводу коллекции).
  • Если вы выполняете много работы и не хотите блокировать другие потоки, работающие одновременно, клонирование коллекции может оказаться вполне приемлемым.
6 голосов
/ 23 декабря 2010

Зависит от вашей модели доступа.Если у вас низкий параллелизм и частые записи, 1 будет иметь лучшую производительность.Если у вас высокий параллелизм и редкие записи, 3 будет иметь лучшую производительность.Вариант 2 будет работать плохо почти во всех случаях.

foreach вызывает iterator(), так что в точности то же самое.

4 голосов
/ 23 декабря 2010

Вы можете использовать одну из более новых коллекций, добавленных в Java 5.0, которые поддерживают параллельный доступ во время итерации.Другой подход заключается в получении копии с использованием toArray, который является поточно-ориентированным (во время копирования).

1 голос
/ 01 апреля 2015

Все три ваших варианта будут работать. Выбор подходящего для вашей ситуации будет зависеть от вашей ситуации.

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

ConcurrentHashMap или "ConcurrentHashSet" (с использованием Collections.newSetFromMap) будут работать, если вам нужен интерфейс Map или Set, очевидно, вы не получите произвольный доступ таким образом. Один отличный! Суть этих двух заключается в том, что они будут хорошо работать с большими наборами данных - при мутировании они просто копируют небольшие кусочки основного хранилища данных.

1 голос
/ 23 декабря 2010

Я предлагаю сбросить Collections.synchronizedXXX и обрабатывать все блокировки единообразно в коде клиента.Базовые коллекции не поддерживают составные операции, полезные для многопоточного кода, и даже если вы используете java.util.concurrent.*, код будет более сложным.Я предлагаю хранить как можно больше кода независимо от потоков.Сохраняйте сложный и подверженный ошибкам потокобезопасный (если нам очень повезло) код как минимум.

1 голос
/ 23 декабря 2010

Я могу быть совершенно не согласен с вашими требованиями, но если вы о них не знаете, ознакомьтесь с google-collection , помня о "неизменности избранного".

0 голосов
/ 23 февраля 2018

Этот вопрос довольно старый (извините, я немного опоздал ..), но я все еще хочу добавить свой ответ.

Я бы выбрал второй вариант (например, клонировать коллекцию перед вызовом итератора ()), но с большим поворотом.

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

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

class ThreadSafeIterator<T> implements Iterator<T> {
    private final Queue<T> clients;
    private T currentElement;
    private final Collection<T> source;

    AsynchronousIterator(final Collection<T> collection) {
        clients = new LinkedList<>(collection);
        this.source = collection;
    }

    @Override
    public boolean hasNext() {
        return clients.peek() != null;
    }

    @Override
    public T next() {
        currentElement = clients.poll();
        return currentElement;
    }

    @Override
    public void remove() {
        synchronized(source) {
            source.remove(currentElement);
        }
    }
}

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

Дело в том, что при использовании такого Итератора никто, ни итеративный, ни итеративный Класс (это реальное слово) не должен беспокоиться о безопасности потоков.

0 голосов
/ 04 января 2011

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

примечание: (toArray () обычно предпочтительнее, за некоторыми исключениями, когда внутренне необходимо создать временный ArrayList). Также обратите внимание, что все, кроме toArray (), также должно быть заключено в синхронизированный (collection), предоставленный с использованием Collections.synchronizedXXX.

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