← Назад к вопросам

Что происходит с итерированием во время добавления в CopyOnWriteArrayList

3.0 Senior🔥 81 комментариев
#Коллекции#Многопоточность

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

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 идеален для сценариев типа слушателей событий или кэшей, а не для работы с часто изменяемыми данными.

Что происходит с итерированием во время добавления в CopyOnWriteArrayList | PrepBro