← Назад к вопросам
Что такое fail-fast и fail-safe итераторы в Java?
2.0 Middle🔥 161 комментариев
#Коллекции
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
# Fail-Fast и Fail-Safe итераторы в Java
Определение
Fail-Fast итератор — выбросит ConcurrentModificationException, если коллекция изменена во время итерации (кроме как через сам итератор).
Fail-Safe итератор — работает с копией коллекции, поэтому изменения во время итерации безопасны.
Fail-Fast итераторы
Как работают
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
// Fail-fast в действии
for (String item : list) {
if (item.equals("B")) {
list.remove(item); // ❌ Выбросит ConcurrentModificationException
}
}
Механизм:
- ArrayList, HashMap, HashSet используют внутренний счётчик
modCount - Каждый раз, когда коллекция изменяется (add, remove, clear),
modCountувеличивается - Итератор помнит значение
modCountпри создании - На каждой итерации проверяет: текущий
modCount== сохранённыйmodCount? - Если нет → ConcurrentModificationException
Реализация в коде
// Упрощённое представление ArrayList
private int modCount = 0;
public Iterator<E> iterator() {
return new Itr();
}
private class Itr implements Iterator<E> {
int expectedModCount = modCount;
public boolean hasNext() {
return cursor != size();
}
public E next() {
checkForComodification(); // Проверка!
return list.get(cursor++);
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
public boolean remove(Object o) {
modCount++; // Счётчик изменился
// ... удаление элемента
}
Какие коллекции fail-fast
- ArrayList
- HashMap
- HashSet
- LinkedList
- TreeMap
- TreeSet
Правильное удаление
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
// ✅ Вариант 1: использовать итератор
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String item = it.next();
if (item.equals("B")) {
it.remove(); // Безопасно — обновляет expectedModCount
}
}
// ✅ Вариант 2: использовать stream
list = list.stream()
.filter(item -> !item.equals("B"))
.collect(Collectors.toList());
// ✅ Вариант 3: removeIf
list.removeIf(item -> item.equals("B"));
Fail-Safe итераторы
Как работают
List<String> list = new CopyOnWriteArrayList<>();
list.add("A");
list.add("B");
list.add("C");
// Fail-safe — работает!
for (String item : list) {
if (item.equals("B")) {
list.remove(item); // ✅ Не выбросит исключение
}
}
Механизм:
- CopyOnWriteArrayList создаёт копию массива при создании итератора
- Итератор работает с копией, оригинал может изменяться
- Новые потоки будут видеть новые данные, но текущий итератор — старые
Реализация
public Iterator<E> iterator() {
// Создаёт снимок текущего состояния
return new COWIterator<>(getArray(), 0);
}
private static class COWIterator<E> {
private final Object[] snapshot; // Копия данных
private int cursor = 0;
COWIterator(Object[] snapshot, int initialCursor) {
this.snapshot = snapshot;
this.cursor = initialCursor;
}
public boolean hasNext() {
return cursor < snapshot.length;
}
public E next() {
return (E) snapshot[cursor++]; // Читает из копии
}
}
Какие коллекции fail-safe
- CopyOnWriteArrayList
- ConcurrentHashMap (частично)
- ConcurrentSkipListMap
Сравнение
| Аспект | Fail-Fast | Fail-Safe |
|---|---|---|
| Изменение во время итерации | ConcurrentModificationException | Работает (но видит старые данные) |
| Производительность итерации | Быстро | Чуть медленнее (из-за копии) |
| Производительность write | Быстро | Медленно (копирует весь массив) |
| Потокобезопасность | Нет | Да (CopyOnWriteArrayList) |
| Память | Минимум | Дополнительная память на копию |
| Используется | По умолчанию | Когда нужна потокобезопасность |
Практические примеры
Проблема fail-fast
List<Integer> numbers = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
// ❌ Выбросит исключение
try {
for (Integer num : numbers) {
if (num % 2 == 0) {
numbers.remove(num);
}
}
} catch (ConcurrentModificationException e) {
System.out.println("Ошибка при удалении во время итерации");
}
Решение с fail-safe
List<Integer> numbers = new CopyOnWriteArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
// ✅ Работает
for (Integer num : numbers) {
if (num % 2 == 0) {
numbers.remove(num); // Безопасно
}
}
// Но итератор уже видел все элементы из копии
Многопоточный сценарий
// CopyOnWriteArrayList для высокой контенции читаемых операций
List<String> subscribers = new CopyOnWriteArrayList<>();
// Поток 1: итерирует
for (String subscriber : subscribers) {
notifyUser(subscriber); // Может быть долго
}
// Поток 2: добавляет новых
subscribers.add("newUser"); // ✅ Не нарушит итерацию Потока 1
Заключение
- Fail-fast — стандарт для обычных коллекций (ArrayList, HashMap), выбрасывает исключение при модификации
- Fail-safe — для многопоточных сценариев (CopyOnWriteArrayList), работает с копией
- Используй removeIf(), iterator.remove(), streams для безопасного удаления из fail-fast коллекций
- Выбирай fail-safe коллекции только если действительно нужна потокобезопасность и высокая контенция читаемых операций