Что такое fail-fast и fail-safe итераторы?
Важно: fail-fast ловит изменения, сделанные любым способом кроме методов самого итератора. Если вы вызываете iterator.remove(), итератор обновляет свой expectedModCount и CME не...
🟢 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-итераторы
- Частые записи — каждая запись копирует весь массив (CopyOnWriteArrayList)
- Большие списки — snapshot потребляет O(n) памяти
- Нужна актуальность данных — 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
- Fail-fast → индикатор багов логики
- Snapshot → для listeners, редких записей
- Weakly Consistent → high-concurrency
- modCount не volatile → best-effort
- removeIf > iterator.remove() для ArrayList
- 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]]