CopyOnWriteArrayList: примеры (JAVA)
CopyOnWriteArrayList: classОписание и поведение
Класс CopyOnWriteArrayList находится в пакете java.util.concurrent. Это потокобезопасная реализация списка с семантикой «копировать при записи»: все мутации (добавление, удаление, замена) выполняют копирование внутренних массивов, тогда как чтения обращаются к неизменяемой снимку массива без блокировок. Такая модель оптимальна для сценариев с частыми операциями чтения и редкими изменениями.
Класс реализует интерфейсы List<E>, RandomAccess, Cloneable, Serializable. Итератор возвращает моментальный снимок содержимого: он не отражает последующие модификации списка и не бросает ConcurrentModificationException. Итератор не поддерживает операцию remove.
Конструкторы, аргументы и возвращаемые значения
CopyOnWriteArrayList()- создаёт пустой список. Возвращаемого значения нет.CopyOnWriteArrayList(Collection<? extends E> c)- создаёт список, скопировав элементы коллекцииc. Еслиcравнаnull, выбрасываетсяNullPointerException.CopyOnWriteArrayList(E[] toCopyIn)- создаёт список на основе массива. Копирование массива происходит при создании.
Основные методы и их поведение
boolean add(E e)- добавляет элемент в конец. Возвращаетtrue(как у большинства коллекций). Выполняет копирование внутреннего массива.void add(int index, E element)- вставка по индексу с копированием. При неверном индексеIndexOutOfBoundsException.boolean addIfAbsent(E e)- добавляет элемент только если его ещё нет; операция атомарна по отношению к другим методам этого списка и возвращаетtrue, если элемент был добавлен.boolean addAll(Collection<? extends E> c)иint addAllAbsent(Collection<? extends E> c)- добавление коллекции с копированием;addAllAbsentдобавляет только отсутствующие элементы и возвращает число добавленных.E get(int index)- получение по индексу без блокировок; при неверном индексеIndexOutOfBoundsException.E set(int index, E element)- замена по индексу; возвращает прежний элемент; выполняется копирование.boolean remove(Object o)иE remove(int index)- удаление с копированием; возвращают успех или удалённый элемент.int size(),boolean isEmpty(),boolean contains(Object o)- операции чтения без блокировок.Iterator<E> iterator()- итератор над снимком в момент вызова; не отражает будущих изменений и не поддерживаетremove.- Методы из Java 8+ (например,
forEach,removeIf,replaceAll) доступны как реализация интерфейсов; при их использовании происходит копирование внутренней структуры при мутации.
Общее правило: операции чтения дешёвые и не блокирующие, операции записи создают новую копию внутреннего массива, что увеличивает расход памяти и стоимость при частых модификациях.
Короткие примеры использования
Примеры демонстрируют основные сценарии: создание, чтение, поведение итератора и специализированные методы.
1. Создание и обычные операции
import java.util.concurrent.CopyOnWriteArrayList;
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
list.add("A");
list.add("B");
String v = list.get(1);
int s = list.size();
v = "B" s = 2
2. Итератор и снимок состояния
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>(new String[]{"x","y"});
for (String t : list) {
// внутри итерации добавляется новый элемент
list.add("z");
System.out.println(t);
}
System.out.println(list);
Вывод: "x" "y" ["x", "y", "z"]
3. addIfAbsent для семантики множества
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
list.add("a");
boolean added = list.addIfAbsent("a");
added = false list = ["a"]
4. Конструктор с коллекцией
List<Integer> src = Arrays.asList(1,2,3);
CopyOnWriteArrayList<Integer> cow = new CopyOnWriteArrayList<>(src);
cow = [1, 2, 3]
Похожие коллекции в Java и их особенности
- Collections.synchronizedList(List) - синхронизированная оболочка вокруг List. Подходит при частых мутациях и при необходимости согласованности итератора (требует внешней синхронизации при итерации). Чтение и запись блокируют одну общую мьютекс.
- CopyOnWriteArraySet - набор, основанный на CopyOnWriteArrayList. Подходит при необходимости множества с редкими изменениями и частыми чтениями.
- Vector - устаревший синхронизированный список. Менее предпочтителен по сравнению с современными альтернативами.
- ConcurrentLinkedQueue - неблокирующая очередь для высоконагруженных сценариев с частыми вставками и удалениями; не обеспечивает произвольного доступа по индексу.
- ConcurrentHashMap / ConcurrentSkipListMap - структуры для карт и наборов с высокой конкурентностью. Предпочтительнее при частых обновлениях и большого числа потоков.
Выбор основывается на балансе чтений и записей, требованиях к консистентности итераторов и необходимости индексации.
Аналоги в других языках и отличия
PHP
Массивы PHP реализованы с семантикой copy-on-write на уровне движка; копирование выполняется при модификации. Однако это не даёт потокобезопасности в многопоточном окружении и не предоставляет специфичных методов, как addIfAbsent.
// PHP-псевдокод
$a = [1,2];
$b = $a; // копирование по ссылке, физическое копирование при записи
$b[] = 3; // теперь создана копия
$a = [1,2] $b = [1,2,3]
JavaScript
В среде Node.js и браузерах однопоточные структуры делают CopyOnWrite редко нужным. Библиотеки вроде Immutable.js предоставляют неизменяемые коллекции с структурным шарингом, что экономит память по сравнению с полным копированием.
// JS с Immutable.js
const { List } = require('immutable');
let l1 = List([1,2]);
let l2 = l1.push(3);
l1 = List [1,2] l2 = List [1,2,3]
Python
Стандартный list не потокобезопасен. Для неизменяемости используется tuple или копирование списка вручную (list.copy()). Для конкурентного доступа применяется threading.RLock или структуры из queue и сторонние immutable-библиотеки.
# Python
lst = [1,2]
lst2 = lst.copy()
lst2.append(3)
lst = [1,2] lst2 = [1,2,3]
C#
В .NET есть System.Collections.Concurrent и пакет System.Collections.Immutable. ImmutableList обеспечивает неизменяемость с эффективным структурным шарингом; для Copy-On-Write семантики в .NET обычно применяется ImmutableList или ручное копирование List<T>.
// C#
var list = ImmutableList.Create(1,2);
var list2 = list.Add(3);
list = [1,2] list2 = [1,2,3]
Go (Golang)
Слайсы в Go копируются при присваивании ссылочно; для паттерна copy-on-write требуется явное копирование с использованием append или копирования с помощью copy(). Для потокобезопасности применяется sync.RWMutex или атомарные операции и immutable-структуры.
// Go
s := []int{1,2}
s2 := append([]int(nil), s...)
s2 = append(s2, 3)
s = [1,2] s2 = [1,2,3]
Kotlin
Kotlin использует Java-коллекции и предоставляет неизменяемые виды через List и MutableList. CopyOnWriteArrayList доступен напрямую из JVM и ведёт себя так же, как в Java.
Lua
Таблицы Lua не имеют встроенной потоковой безопасности. Для copy-on-write требуется ручное копирование таблицы при изменении и внешняя синхронизация при использовании в многопоточном окружении (корутины обычно не используют параллелизм в стандартной реализации).
В заключение, в большинстве языков есть либо внутренний copy-on-write (как в PHP для массивов) или функции для создания неизменяемых/иммутабельных коллекций; отличия касаются многопоточности, стоимости копирования и наличия атомарных методов типа addIfAbsent.
Типичные ошибки и заблуждения
- Ожидание, что итератор будет отражать последующие изменения. На деле итератор работает со снимком; изменения, выполненные после создания итератора, не будут видны. Пример:
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>(Arrays.asList("a","b"));
Iterator<String> it = list.iterator();
list.add("c");
while (it.hasNext()) {
System.out.println(it.next());
}
Выведет: a b // "c" не будет выведен итератором
- Использование в сценариях с частыми записями. Полное копирование при каждой мутации приводит к высоким накладным расходам по времени и памяти. Предпочтительнее выбирать конкурентные коллекции с более дешёвыми мутациями при высокой интенсивности записи.
- Попытка вызывать
removeна итераторе. Итератор снимка не поддерживает модификации через себя - будет брошеноUnsupportedOperationException. - Недооценка расхода памяти при больших списках: каждая мутация создаёт новую копию массива, пока прежние копии не будут собраны сборщиком мусора.
- Ожидание атомарности комплексных операций. Хотя отдельные методы, такие как
addIfAbsent, атомарны, комбинации операций (например, проверка и затем вставка несколькими вызовами) не являются атомарными без внешней синхронизации.
Изменения и история развития
Класс CopyOnWriteArrayList введён в Java 5 как часть пакета java.util.concurrent. В Java 8 и более поздних версиях появились методы потокового API и default-методы в коллекциях (forEach, removeIf, replaceAll и т.д.), которые могут использоваться с CopyOnWriteArrayList; они сохраняют семантику копирования при мутациях. Специфичных кардинальных изменений в самой реализации класса в последних версиях не было, важные изменения касались совместимости с новыми API коллекций и улучшений производительности JVM в части копирования и сборки мусора.
Расширенные примеры и нестандартные сценарии
1. Менеджер слушателей (listeners) в многопоточной среде
import java.util.concurrent.CopyOnWriteArrayList;
public class EventBus {
private final CopyOnWriteArrayList<Runnable> listeners = new CopyOnWriteArrayList<>();
public void register(Runnable r) { listeners.add(r); }
public void unregister(Runnable r) { listeners.remove(r); }
public void fire() {
for (Runnable r : listeners) {
r.run(); // нет внешней синхронизации, безопасно для чтения
}
}
}
Поведение: несколько потоков могут регистрировать/удалять слушателей, а вызов fire() безопасно переберет снимок слушателей на момент вызова.
2. Использование addIfAbsent для реализации множеств с атомарной вставкой
CopyOnWriteArrayList<String> setLike = new CopyOnWriteArrayList<>();
// параллельный поток A
setLike.addIfAbsent("k");
// параллельный поток B
setLike.addIfAbsent("k");
После конкурентных вызовов элемент "k" будет добавлен не более одного раза благодаря атомарности addIfAbsent.
3. Сравнение накладных расходов: простая микро-оценка
// Псевдокод для измерения времени добавления
CopyOnWriteArrayList<Integer> cow = new CopyOnWriteArrayList<>();
for (int i = 0; i < 100_000; i++) cow.add(i);
// vs
List<Integer> sync = Collections.synchronizedList(new ArrayList<>());
for (int i = 0; i < 100_000; i++) sync.add(i);
Результат зависит от нагрузки и числа потоков: при одиночном потоке ArrayList быстрее; при многих потоках и в сценариях чтений CopyOnWriteArrayList может показать лучшие показатели за счёт отсутствия блокировок на чтение.
4. Комбинация с volatile и ссылочной семантикой
Иногда хранение ссылки на CopyOnWriteArrayList в volatile-поле позволяет быстро менять всю структуру списка целиком и гарантировать видимость обновления всем потокам.
volatile CopyOnWriteArrayList<String> ref = new CopyOnWriteArrayList<>();
// обновление полной версии
CopyOnWriteArrayList<String> newVer = new CopyOnWriteArrayList<>(ref);
newVer.add("extra");
ref = newVer; // атомарная смена ссылки
Другие потоки после присваивания увидят новую версию целиком без необходимости синхронизации.
5. Использование с параллельными потоками и стримами
CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<>(Arrays.asList(1,2,3,4));
list.parallelStream().forEach(System.out::println);
Вывод элементов с возможным произвольным порядком. Параллельный стрим не модифицирует исходный список, и при модификациях во время стрима поведение определяется снимком данных.
6. Нетипичный сценарий: частичная массовая замена
Для замены большого количества элементов предпочтительнее собрать новую коллекцию и подставить её целиком (через присваивание ссылки или создание нового экземпляра), чтобы минимизировать число копирований.
CopyOnWriteArrayList<String> current = new CopyOnWriteArrayList<>(old);
List<String> modified = current.stream()
.map(s -> transform(s))
.collect(Collectors.toList());
CopyOnWriteArrayList<String> replaced = new CopyOnWriteArrayList<>(modified);
current = replaced; // переключить ссылку atomically если поле volatile
Результат: единственное создание копии вместо множества промежуточных копий при последовательных set/add.
В целом CopyOnWriteArrayList хорош в паттернах «много чтений, редкие записи», для реализации менеджеров слушателей и для упрощения логики конкурентного доступа, когда стоимость копирования остаётся приемлемой.
джава CopyOnWriteArrayList function comments
- джава CopyOnWriteArrayList - аргументы и возвращаемое значение
- Функция java CopyOnWriteArrayList - описание
- CopyOnWriteArrayList - примеры
- CopyOnWriteArrayList - похожие методы на java
- CopyOnWriteArrayList на javascript, c#, python, php
- CopyOnWriteArrayList изменения java
- Примеры CopyOnWriteArrayList на джава