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.
Пишите мне свои вопросы в комментариях.