Что происходит с итерированием во время добавления в CopyOnWriteArrayList
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
CopyOnWriteArrayList: итерирование при добавлении элементов
CopyOnWriteArrayList — это потокобезопасный список, который использует стратегию «копирование при записи» (Copy-On-Write). Это имеет важные последствия для итерирования во время добавления элементов.
Основная стратегия Copy-On-Write
Вместо использования блокировок для синхронизации чтения и записи, CopyOnWriteArrayList копирует весь внутренний массив при каждом добавлении, удалении или изменении элемента:
// Упрощённая логика CopyOnWriteArrayList
public void add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
// Копируем весь массив + новый элемент
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
// Заменяем ссылку на новый массив
setArray(newElements);
} finally {
lock.unlock();
}
}
Что происходит при итерировании?
Критический момент: Когда один поток итерирует список, а другой добавляет элементы, итератор видит снимок (snapshot) списка на момент создания итератора.
public class CopyOnWriteIterationExample {
public static void main(String[] args) throws InterruptedException {
CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<>();
list.add(1);
list.add(2);
list.add(3);
// Поток 1: итерирует список
Thread iteratorThread = new Thread(() -> {
Iterator<Integer> iterator = list.iterator(); // Снимок: [1, 2, 3]
while (iterator.hasNext()) {
Integer value = iterator.next();
System.out.println("Iterating: " + value);
try { Thread.sleep(100); } catch (InterruptedException e) {}
}
});
// Поток 2: добавляет элементы
Thread writerThread = new Thread(() -> {
try { Thread.sleep(150); } catch (InterruptedException e) {}
list.add(4);
System.out.println("Added 4");
list.add(5);
System.out.println("Added 5");
});
iteratorThread.start();
writerThread.start();
iteratorThread.join();
writerThread.join();
System.out.println("Final list: " + list); // [1, 2, 3, 4, 5]
}
}
Вывод:
Iterating: 1
Iterating: 2
Added 4
Iterating: 3
Added 5
Final list: [1, 2, 3, 4, 5]
Итератор видит только [1, 2, 3], хотя в списке теперь [1, 2, 3, 4, 5]!
Ключевые характеристики
Безопасность от ConcurrentModificationException
// ArrayList выбросит исключение:
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
for (Integer value : list) {
if (value == 1) {
list.add(3); // ConcurrentModificationException!
}
}
// CopyOnWriteArrayList НЕ выбросит исключение:
List<Integer> cowList = new CopyOnWriteArrayList<>();
cowList.add(1);
cowList.add(2);
for (Integer value : cowList) {
if (value == 1) {
cowList.add(3); // OK! Итератор не заметит изменения
}
}
Преимущества и недостатки
Преимущества:
- Безопасность для чтения без блокировок
- Нет ConcurrentModificationException
- Хорошо для сценариев с частым чтением и редким написанием
Недостатки:
- Высокие затраты на добавление/удаление (копируется весь массив)
- Требует много памяти
- Медленная запись
- Плохо для списков с частыми изменениями
Когда использовать?
// Хорошо: частое чтение, редкое добавление
CopyOnWriteArrayList<String> eventListeners = new CopyOnWriteArrayList<>();
// Плохо: частые добавления в цикле
CopyOnWriteArrayList<Integer> mutableData = new CopyOnWriteArrayList<>();
for (int i = 0; i < 1_000_000; i++) {
mutableData.add(i); // МЕДЛЕННО! Копирует весь массив каждый раз
}
CopyOnWriteArrayList идеален для сценариев типа слушателей событий или кэшей, а не для работы с часто изменяемыми данными.