Вопрос 26 · Раздел 4

Что такое fail-fast и fail-safe итераторы?

Важно: fail-fast ловит изменения, сделанные любым способом кроме методов самого итератора. Если вы вызываете iterator.remove(), итератор обновляет свой expectedModCount и CME не...

Версии по языкам: English Russian Ukrainian

🟢 Junior Level

Fail-fast — итератор сразу бросает исключение, если коллекцию изменили. Fail-safe — итератор продолжает работать, игнорируя изменения.

// Fail-fast (ArrayList, HashMap)
List<String> list = new ArrayList<>(List.of("A", "B"));
for (String s : list) {
    list.add("C");  // → ConcurrentModificationException!
}

// Fail-safe (CopyOnWriteArrayList, ConcurrentHashMap)
List<String> safe = new CopyOnWriteArrayList<>(List.of("A", "B"));
for (String s : safe) {
    safe.add("C");  // → OK (итератор не видит "C")
}

🟡 Middle Level

Fail-fast: modCount механизм

// Коллекция хранит modCount (число изменений)
// Итератор копирует expectedModCount при создании

// При каждом next():
if (modCount != expectedModCount) {
    throw new ConcurrentModificationException();
}

Важно: fail-fast ловит изменения, сделанные любым способом кроме методов самого итератора. Если вы вызываете iterator.remove(), итератор обновляет свой expectedModCount и CME не возникнет. Это ловушка для отлова багов в одном потоке, а не механизм синхронизации.

Типы “fail-safe”

Snapshot-based:

// CopyOnWriteArrayList
Iterator<String> it = list.iterator();  // Snapshot массива
list.add("X");  // Новый массив
it.next();  // → Из старого массива (не видит "X")

Weakly Consistent:

// ConcurrentHashMap
Iterator<String> it = map.keySet().iterator();
map.put("X", 1);  // Живые данные
it.next();  // → МОЖЕТ увидеть "X" (но не обязан!)
// → Как правило, не бросает CME (для ConcurrentHashMap — гарантированно нет)

Сравнение

  Fail-fast Snapshot Weakly Consistent
Exception Да Нет Нет
Копия Нет Да Нет
Актуальность Строгая Устаревшая Приблизительная

🔴 Senior Level

Когда НЕ использовать snapshot-итераторы

  1. Частые записи — каждая запись копирует весь массив (CopyOnWriteArrayList)
  2. Большие списки — snapshot потребляет O(n) памяти
  3. Нужна актуальность данных — snapshot устаревает мгновенно после создания

modCount не volatile!

// В многопоточной среде:
// Поток 1 изменяет → modCount++
// Поток 2 может НЕ увидеть (cache coherence)!
// → CME не гарантирован в multi-threading!

// Это best-effort, не 100% защита

volatile — ключевое слово, гарантирующее видимость изменений между потоками. Без volatile один поток может записать modCount++, а другой поток (из своего CPU-кэша) увидеть старое значение.

Best-effort — механизм пытается поймать ошибку, но не гарантирует в multi-threading. Fail-fast — это детектор багов, а не механизм синхронизации.

removeIf оптимизация

// ❌ O(n²) для ArrayList
for (Iterator<String> it = list.iterator(); it.hasNext(); ) {
    if (condition(it.next())) {
        it.remove();  // System.arraycopy каждый раз!
    }
}

// ✅ O(n) один проход
list.removeIf(e -> condition(e));
// → Сначала находит все для удаления
// → Потом ОДИН сдвиг массива

Snapshot memory cost

CopyOnWriteArrayList: 1 млн элементов
  → Iterator = ссылка на массив (4 МБ)
  → add() = новый массив (4 МБ)
  → Временно: 8 МБ!

Best Practices

  1. Fail-fast → индикатор багов логики
  2. Snapshot → для listeners, редких записей
  3. Weakly Consistent → high-concurrency
  4. modCount не volatile → best-effort
  5. removeIf > iterator.remove() для ArrayList
  6. iterator.remove() → единственный безопасный способ в fail-fast

Резюме для Senior

  • Fail-fast = modCount проверка, best-effort
  • Snapshot = копия массива, изоляция
  • Weakly Consistent = живые данные, без Exception
  • modCount не volatile → не 100% в multi-threading
  • removeIf = O(n) оптимизация

🎯 Шпаргалка для интервью

Обязательно знать:

  • Fail-fast итераторы бросают ConcurrentModificationException при изменении коллекции (ArrayList, HashMap)
  • Механизм: сравнение modCount коллекции с expectedModCount итератора
  • Fail-fast — это детектор багов в одном потоке, а не механизм синхронизации
  • Snapshot-итераторы (CopyOnWriteArrayList) — копия массива на момент создания, не видят последующие изменения
  • Weakly Consistent (ConcurrentHashMap) — живые данные, без CME, но не гарантируют актуальность
  • modCount НЕ volatile → в многопоточности CME не гарантирован (best-effort)
  • iterator.remove() — единственный безопасный способ удаления при fail-fast итерации
  • removeIf() лучше iterator.remove() для ArrayList — O(n) вместо O(n^2)

Частые уточняющие вопросы:

  • Почему fail-fast не гарантирует CME в многопоточности?modCount не volatile; поток может не увидеть изменение из-за cache coherence.
  • Что видит итератор CopyOnWriteArrayList после add()? — Старые данные (snapshot); новые элементы не видны.
  • Чем Snapshot отличается от Weakly Consistent? — Snapshot — копия (устаревшие данные), Weakly Consistent — живые данные (могут видеть изменения).
  • Какова цена snapshot для 1 млн элементов? — O(n) памяти; копирование массива ~4 МБ, при add() — временно 8 МБ.

Красные флаги (НЕ говорить):

  • «Fail-fast гарантирует обнаружение изменений в многопоточности» — нет, modCount не volatile, best-effort
  • «Snapshot-итератор видит новые элементы» — нет, snapshot — копия на момент создания
  • «CopyOnWriteArrayList подходит для частых записей» — нет, каждая запись = копия всего массива
  • «Fail-fast — это механизм синхронизации» — нет, это детектор багов логики

Связанные темы:

  • [[27. Что такое ConcurrentModificationException]]
  • [[28. Как правильно удалять элементы во время итерации]]
  • [[21. Когда использовать synchronized collections]]