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 resultsBezpieczne 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 copyIterator 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 consistentCollections.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 threadWzorzec 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
- Kontrakty Iterable i Iterator
- Implementowanie własnego iteratora
- ListIterator i przechodzenie dwukierunkowe
- Iteratory fail-fast a fail-safe