0Pricing
Java Academy · Lekcja

Iteratory fail-fast a fail-safe

Poznaj ConcurrentModificationException i działanie fail-fast oraz dowiedz się, kiedy używać iteratorów migawkowych.

Iteratory fail-fast a fail-safe to bezpłatna lekcja Java Academy na CoddyKit. To lekcja 4 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej Java Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs Java Academy zawiera 4 lekcji w sumie.

Iteratory fail-fast i fail-safe

Iteratory Javy klasyfikuje się na podstawie ich zachowania, gdy podczas iteracji modyfikowana jest bazowa kolekcja: iteratory fail-fast natychmiast zgłaszają wyjątek, a iteratory fail-safe kontynuują działanie na migawce.

Iteratory fail-fast

Większość standardowych kolekcji Javy (ArrayList, HashMap, TreeSet) używa iteratorów fail-fast. Śledzą one wartość modCount — licznik modyfikacji. Każda modyfikacja strukturalna podczas iteracji powoduje zgłoszenie ConcurrentModificationException.

import java.util.*;

List<String> list = new ArrayList<>(List.of("a", "b", "c"));

try {
    for (String s : list) {
        list.add("x"); // structural modification — throws!
    }
} catch (ConcurrentModificationException e) {
    System.out.println("ConcurrentModificationException caught!");
}
// Same for HashMap, TreeMap, HashSet, etc.

Dlaczego istnieje fail-fast

Zachowanie fail-fast pomaga w debugowaniu — natychmiast ujawnia błędy, zamiast pozwalać na kontynuowanie iteracji ze skorumpowanym stanem. Alternatywa w postaci cichego uszkodzenia danych jest znacznie trudniejsza do zdebugowania.

// modCount is incremented on every structural change
// (add, remove, clear on ArrayList)
// Iterator records modCount on creation
// On each next(), Iterator checks: if current modCount != expected, throw CME

// This detects bugs like:
List<Integer> nums = new ArrayList<>(List.of(1,2,3,4,5));
for (Integer n : nums) {
    if (n == 3) nums.remove(n); // bug caught immediately
}
// ConcurrentModificationException — not silent wrong results

Bezpieczne usuwanie: Iterator.remove()

Jedyną bezpieczną modyfikacją podczas iteracji opartej na Iteratorze jest Iterator.remove() — usuwa ostatni zwrócony element i aktualizuje modCount.

List<Integer> nums = new ArrayList<>(List.of(1,2,3,4,5,6));
Iterator<Integer> it = nums.iterator();

while (it.hasNext()) {
    int n = it.next();
    if (n % 2 == 0) it.remove(); // safe: updates modCount
}

System.out.println(nums); // [1, 3, 5]

removeIf: nowoczesna alternatywa

W Javie 8 do Collection dodano removeIf(Predicate) — jest to prostszy sposób usuwania elementów spełniających warunek, bez ręcznego zarządzania iteratorem.

List<String> names = new ArrayList<>(List.of("Alice", "Bob", "Ann", "Charlie"));

// Modern: removeIf handles iteration internally
names.removeIf(name -> name.startsWith("A"));
System.out.println(names); // [Bob, Charlie]

// Equivalent but verbose iterator approach:
Iterator<String> it = names.iterator();
while (it.hasNext()) {
    if (it.next().startsWith("A")) it.remove();
}

Iteratory fail-safe: CopyOnWriteArrayList

CopyOnWriteArrayList używa iteratora fail-safe. Iteruje on po migawce utworzonej w chwili utworzenia iteratora — modyfikacje nie wpływają na trwającą iterację.

import java.util.concurrent.CopyOnWriteArrayList;

CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
list.addAll(List.of("a", "b", "c"));

for (String s : list) {
    list.add("x"); // no ConcurrentModificationException!
    System.out.print(s + " "); // prints a, b, c (snapshot)
}
System.out.println();
System.out.println(list); // [a, b, c, x, x, x] — modified copy

Iterator ConcurrentHashMap

ConcurrentHashMap używa iteratorów o słabej spójności — odzwierciedlają one stan z pewnego momentu w trakcie tworzenia iteratora lub później, nie zgłaszają CME i mogą uwzględniać równoczesne aktualizacje albo nie.

import java.util.concurrent.ConcurrentHashMap;

ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.put("a", 1); map.put("b", 2); map.put("c", 3);

for (Map.Entry<String, Integer> e : map.entrySet()) {
    map.put("d", 4); // no CME — weakly consistent
    System.out.println(e.getKey() + "=" + e.getValue());
}
// May or may not print "d=4" — weakly consistent

Collections.synchronizedList

Collections.synchronizedList NIE jest fail-safe — nadal używa iteratorów fail-fast, ale blokuje równoczesny dostęp. Podczas iteracji należy ręcznie synchronizować dostęp.

import java.util.*;

List<String> syncList = Collections.synchronizedList(new ArrayList<>());
syncList.addAll(List.of("a", "b", "c"));

// Must synchronize during iteration!
synchronized (syncList) {
    for (String s : syncList) {
        System.out.println(s);
    }
}
// Without the synchronized block, CME is still possible from another thread

Wzorzec iteratora migawkowego

Należy ręcznie utworzyć migawkę, aby bezpiecznie iterować po modyfikowalnej kolekcji bez synchronizacji.

import java.util.*;

List<String> original = new ArrayList<>(List.of("a", "b", "c"));

// Take a snapshot copy before iterating
List<String> snapshot = List.copyOf(original);

for (String s : snapshot) {
    // Safe to modify original during snapshot iteration
    original.remove(s);
    System.out.println("Removed: " + s);
}
System.out.println(original); // []

Kompromisy CopyOnWrite

CopyOnWriteArrayList jest fail-safe, ale kosztowny podczas zapisu. Każda modyfikacja tworzy pełną kopię.

  • Dobre rozwiązanie w przypadku: wielu odczytów i rzadkich zapisów (listy odbiorników zdarzeń)
  • Złe rozwiązanie w przypadku: częstych modyfikacji i dużych kolekcji

Podsumowanie: fail-fast i fail-safe

Podsumowanie porównania:

  • Fail-fast: ArrayList, HashMap, TreeMap — przy modyfikacji zgłaszają CME i sprawdzają modCount
  • Fail-safe/słabo spójne: ConcurrentHashMap, CopyOnWriteArrayList — nie zgłaszają CME, iterują po migawce lub równocześnie modyfikowanych danych
  • Bezpieczne metody modyfikacji: Iterator.remove(), removeIf(), replaceAll()

Praktyczny przykład: czyszczenie wygasłych sesji

Bezpieczne usuwanie wygasłych sesji ze współbieżnej listy z użyciem odpowiednich wzorców.

import java.util.*;
import java.util.concurrent.*;

class SessionManager {
    private final CopyOnWriteArrayList<Session> sessions = new CopyOnWriteArrayList<>();

    record Session(String id, long expiresAt) {
        boolean isExpired() { return System.currentTimeMillis() > expiresAt; }
    }

    void add(Session s) { sessions.add(s); }

    // Safe to call from multiple threads while iterating
    void purgeExpired() {
        sessions.removeIf(Session::isExpired);
    }

    List<Session> active() {
        return sessions.stream().filter(s -> !s.isExpired()).toList();
    }
}

Szybkie sprawdzenie

Jaki wyjątek jest zgłaszany podczas modyfikowania ArrayList w trakcie iteracji?

Podsumowanie: iteratory fail-fast i fail-safe

Najważniejsze informacje:

  • Iteratory fail-fast (ArrayList, HashMap) zgłaszają ConcurrentModificationException przy równoczesnej modyfikacji
  • Iteratory fail-safe (CopyOnWriteArrayList, ConcurrentHashMap) pozwalają na modyfikację bez zgłaszania wyjątku
  • Iterator.remove() to jedyny bezpieczny sposób usuwania elementów podczas iteracji fail-fast
  • removeIf() to nowoczesna i prostsza alternatywa dla ręcznego używania Iterator.remove()
  • CopyOnWriteArrayList: bezpieczny w scenariuszach z przewagą odczytów i rzadkimi zapisami
  • ConcurrentHashMap: słabo spójny — nie zgłasza CME i może uwzględniać równoczesne modyfikacje albo nie

Często zadawane pytania

Czy lekcja „Iteratory fail-fast a fail-safe” jest bezpłatna?

Tak — pełny tekst „Iteratory fail-fast a fail-safe” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu Java Academy, przejdź na CoddyKit PRO. Kurs Java Academy zawiera 4 lekcji w sumie.

Co nauczysz się w „Iteratory fail-fast a fail-safe”?

Poznaj ConcurrentModificationException i działanie fail-fast oraz dowiedz się, kiedy używać iteratorów migawkowych. Ćwiczysz Java Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.

Czy potrzebuję doświadczenia, aby zacząć Java Academy?

Nie wymagamy żadnego doświadczenia. Java Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 4 z 4.

Ile czasu zajmuje lekcja „Iteratory fail-fast a fail-safe”?

Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.

Czy mogę pisać i uruchamiać kod w tej lekcji Java Academy?

Tak. Każda lekcja Java Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.

Wszystkie lekcje w tym kursie

  1. Kontrakty Iterable i Iterator
  2. Implementowanie własnego iteratora
  3. ListIterator i przechodzenie dwukierunkowe
  4. Iteratory fail-fast a fail-safe
← Powrót do Java Academy