Java Academy · Lektion

Fail-fast vs. fail-safe-iteratorer

Forstå ConcurrentModificationException og fail-fast-adfærd, og hvornår De skal bruge snapshot-iteratorer.

Lektion 4 af 414 trin

Fail-fast vs. fail-safe-iteratorer er en gratis Java Academy-lektion på CoddyKit. Dette er lektion 4 af 4. Du kan læse hele lektionen gratis nedenfor — og derefter øve dig praktisk i browseren med en indbygget kodeeditor og en AI-vejleder, der er tilgængelig døgnet rundt. Den er en del af læringsforløbet i Java Academy, og dine fremskridt synkroniseres på tværs af nettet og CoddyKit-appen. Java Academy-kurset indeholder 4 lektioner i alt.

Fail-fast- og fail-safe-iteratorer

Java-iteratorer kategoriseres efter deres adfærd, når den underliggende samling ændres under iteration: fail-fast-iteratorer kaster straks en undtagelse, mens fail-safe-iteratorer fortsætter på et øjebliksbillede.

Fail-fast-iteratorer

De fleste almindelige Java-samlinger (ArrayList, HashMap og TreeSet) bruger fail-fast-iteratorer. De holder styr på en modCount — en tæller for ændringer. Enhver strukturel ændring under iteration udløser 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.

Hvorfor fail-fast findes

Fail-fast-adfærd er en hjælp til fejlfinding — den afslører fejl med det samme i stedet for at lade iterationen fortsætte med en ødelagt tilstand. Alternativet (ubemærket ødelæggelse af data) er langt sværere at fejlfinde.

// 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

Sikker fjernelse: Iterator.remove()

Den eneste sikre ændring under en Iterator-baseret iteration er Iterator.remove() — den fjerner det senest returnerede element og opdaterer 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: Det moderne alternativ

Java 8 tilføjede removeIf(Predicate) til Collection — en renere måde at fjerne elementer, der opfylder en betingelse, uden manuel håndtering af iteratoren.

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();
}

Fail-safe-iteratorer: CopyOnWriteArrayList

CopyOnWriteArrayList bruger en fail-safe-iterator. Den gennemløber et øjebliksbillede, der blev taget, da iteratoren blev oprettet — ændringer påvirker ikke det igangværende gennemløb.

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

ConcurrentHashMap-iterator

ConcurrentHashMap bruger svagt konsistente iteratorer — de afspejler tilstanden på et tidspunkt under eller efter oprettelsen af iteratoren. Der kastes ingen CME, og iteratoren ser måske de samtidige opdateringer, måske ikke.

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 er IKKE fail-safe — den bruger stadig fail-fast-iteratorer, men blokerer samtidig adgang. Du skal selv synkronisere under gennemløbet.

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

Mønsteret med en øjebliksbillede-iterator

Opret selv et øjebliksbillede, så du sikkert kan gennemløbe en ændrbar samling uden synkronisering.

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); // []

Afvejninger ved CopyOnWrite

CopyOnWriteArrayList er fail-safe, men dyr ved skriveoperationer. Hver ændring opretter en fuld kopi.

  • Godt til: mange læsninger og få skrivninger (lister med hændelseslyttere)
  • Dårligt til: hyppige ændringer og store samlinger

Opsamling: Fail-fast og fail-safe

Opsummering af sammenligningen:

  • Fail-fast: ArrayList, HashMap og TreeMap — kaster CME ved ændringer og kontrollerer modCount
  • Fail-safe/svagt konsistent: ConcurrentHashMap og CopyOnWriteArrayList — ingen CME, gennemløber et øjebliksbillede eller samtidige data
  • Sikre ændringsmetoder: Iterator.remove(), removeIf() og replaceAll()

Praktisk: Oprydning efter udløbne sessioner

Fjern udløbne sessioner sikkert fra en samtidig liste ved hjælp af passende mønstre.

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();
    }
}

Hurtigt tjek

Hvilken undtagelse kastes, hvis du ændrer en ArrayList under gennemløbet?

Opsamling: Fail-fast- og fail-safe-iteratorer

Vigtigste pointer:

  • Fail-fast-iteratorer (ArrayList og HashMap) kaster ConcurrentModificationException ved samtidige ændringer
  • Fail-safe-iteratorer (CopyOnWriteArrayList og ConcurrentHashMap) tillader ændringer uden at kaste en undtagelse
  • Iterator.remove() er den eneste sikre måde at fjerne elementer under et fail-fast-gennemløb
  • removeIf() er det moderne og renere alternativ til manuel Iterator.remove()
  • CopyOnWriteArrayList: sikker i scenarier med mange læsninger og få skrivninger
  • ConcurrentHashMap: svagt konsistent — ingen CME, og samtidige ændringer ses måske, måske ikke
Gratis at komme i gang

Lær Java med en AI-underviser — gratis

Skriv og kør rigtig kode i din browser, få øjeblikkelig hjælp fra en AI-underviser døgnet rundt, og fortsæt, hvor du slap, på web eller i appen.

Kurser
104
Lektioner
374

Ofte stillede spørgsmål

Er lektionen “Fail-fast vs. fail-safe-iteratorer” gratis?

Ja — hele teksten til “Fail-fast vs. fail-safe-iteratorer” kan læses gratis her på nettet. Hvis du vil øve dig interaktivt med en indbygget kodeeditor og en AI-vejleder døgnet rundt og få adgang til resten af Java Academy-kurset, skal du opgradere til CoddyKit PRO. Java Academy-kurset indeholder 4 lektioner i alt.

Hvad lærer jeg i “Fail-fast vs. fail-safe-iteratorer”?

Forstå ConcurrentModificationException og fail-fast-adfærd, og hvornår De skal bruge snapshot-iteratorer. Du øver dig i Java Academy med praktisk kode, som du kører direkte i browseren, og en AI-vejleder døgnet rundt besvarer dine spørgsmål, mens du arbejder dig gennem lektionen.

Skal jeg have erfaring for at begynde på Java Academy?

Der kræves ingen tidligere erfaring. Java Academy på CoddyKit er tilrettelagt for både begyndere og øvede, så du kan starte her eller fra begyndelsen og lære i dit eget tempo. Dette er lektion 4 af 4.

Hvor lang tid tager lektionen “Fail-fast vs. fail-safe-iteratorer”?

De fleste CoddyKit-lektioner tager cirka 5–10 minutter. Hver lektion er kort og interaktiv, så du gør løbende fremskridt og kan fortsætte, hvor du slap – på både web og app.

Kan jeg skrive og køre kode i denne Java Academy-lektion?

Ja. Alle Java Academy-lektioner har en indbygget kodeeditor, så du kan skrive og køre rigtig kode direkte i din browser og få øjeblikkelig feedback fra AI – uden lokal opsætning.

Alle lektioner i dette kursus

  1. Kontrakterne for Iterable og Iterator
  2. Implementering af en brugerdefineret iterator
  3. ListIterator og tovejs-gennemløb
  4. Fail-fast vs. fail-safe-iteratorer
← Tilbage til Java Academy