Java-класс CopyOnWriteArraySet

CopyOnWriteArraySet является частью пакета java.util.concurrent и является потокобезопасным вариантом Set и поддерживается CopyOnWriteArrayList. Он весьма полезен в сценариях, где чтения происходят чаще, чем записи.

1. Как работает CopyOnWriteArraySet?

    Подобно CopyOnWriteArrayList, это неизменяемый метод итератора в стиле моментального снимка. Любое изменение коллекции создает новую копию базового массива.

    • Когда мы вызываем add(), он проверяет, существует ли уже элемент в базовом массиве:
      • Если элемент отсутствует, он создает новую копию массива, добавляет в нее элемент и заменяет старый массив новым.
      • Если элемент уже существует, набор остается неизменным.
    • Когда мы вызываем remove(), он создает новую копию массива, исключая элемент, который нужно удалить. Старый массив отбрасывается, а новый массив становится активной структурой данных.
    • При итерации итерация происходит на моментальном снимке массива, который существовал на момент создания итератора.
      • Итераторы неизменяемы, и никакие изменения набора во время итерации не влияют на итератор.
      • Исключение ConcurrentModificationException не генерируется даже в параллельных средах.

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

    2. Синтаксис

    Создание CopyOnWriteArraySet просто. Здесь E представляет тип элементов, которые будет содержать набор.

    import java.util.concurrent.CopyOnWriteArraySet;CopyOnWriteArraySet<E> set = new CopyOnWriteArraySet<>()

    3. Распространенные методы

    При взаимодействии с CopyOnWriteArraySet обычно используются следующие методы.

    Метод Описание
    добавить(Е е) Добавляет указанный элемент, если он еще отсутствует.
    добавить(коллекция) Добавляет все элементы указанной коллекции в набор.
    удалить(Объект o) Удаляет указанный элемент, если он присутствует.
    содержит(Объект o) Проверяет, содержит ли набор указанный элемент.
    размер() Возвращает количество элементов в наборе.
    итератор() Возвращает итератор по элементам.
    прозрачный() Удаляет все элементы из набора.
    isEmpty() Возвращает true, если набор не содержит элементов.

    4. Пример CopyOnWriteArraySet

    Давайте рассмотрим простой пример использования класса CopyOnWriteArraySet.

    CopyOnWriteArraySet<String> set = new CopyOnWriteArraySet<>();// Adding elementsset.add("Apple");set.add("Banana");set.add("Cherry");set.add("Apple"); // Duplicate element, won't be added// Display the setSystem.out.println("Set: " + set); // Output: [Apple, Banana, Cherry]// Check for an elementSystem.out.println("Contains 'Banana': " + set.contains("Banana")); // Output: true// Remove an elementset.remove("Banana");System.out.println("Set after removal: " + set); // Output: [Apple, Cherry]

    5. Исключение ConcurrentModificationException не выбрасывается.

    Когда итератор получается из CopyOnWriteArraySet, он работает с моментальным снимком массива на момент создания итератора. Поскольку каждая модификация создает новый массив, различные потоки, работающие одновременно с CopyOnWriteArraySet, по сути работают с различными массивами.

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

    CopyOnWriteArraySet<Integer> set = new CopyOnWriteArraySet<>();set.add(1);set.add(2);set.add(3);// Start a thread to modify the setnew Thread(() -> {set.add(4);set.remove(1);}).start();// Iterate over the setfor(int num : set) {System.out.println("Element: " + num);// Even if the set is modified, no ConcurrentModificationException is thrown}

    6. Когда использовать?

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

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

    import java.util.concurrent.CopyOnWriteArraySet;public class ListenerManager {private final CopyOnWriteArraySet<String> listeners = new CopyOnWriteArraySet<>();public void addListener(String listener) {listeners.add(listener);}public void removeListener(String listener) {listeners.remove(listener);}public void notifyListeners(String event) {for(String listener : listeners) {System.out.println("Notifying " + listener + " of event: " + event);}}public static void main(String[] args) {ListenerManager manager = new ListenerManager();manager.addListener("Listener1");manager.addListener("Listener2");manager.notifyListeners("Event1");}}

    CopyOnWriteArraySet помогает минимизировать шаги синхронизации, контролируемые программистом, и передает управление встроенным, хорошо протестированным API.

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

    7. Сравнение с другими коллекциями

    Давайте сравним CopyOnWriteArraySet с другими наборами в фреймворке коллекции:

    Особенность КопироватьПриЗаписиНаборМассива HashSet ConcurrentHashMap
    Потокобезопасный Да Нет Да
    Позволяет дублировать Нет Нет Нет
    Производительность по чтению Быстрый Быстрый Быстрый
    Производительность по записям Дорого(копирование по записи) Быстрый Умеренный
    Последовательный снимок во время итерации Да Нет Нет

    8. Заключение

    Этот урок Java Collection научил нас использовать класс CopyOnWriteArraySet, его конструкторы, методы и варианты использования. Мы изучили внутреннюю работу CopyOnWriteArraySet и то, чем он отличается от CopyOnWriteArrayList.

    Пишите мне свои вопросы в комментариях.

    Прокрутить вверх