Java Academy · Oppitunti

Fail-fast- ja fail-safe-iteraattorit

Ymmärrä ConcurrentModificationException-poikkeus ja fail-fast-toiminta sekä tiedä, milloin snapshot-iteraattoreita kannattaa käyttää.

Oppitunti 4/414 vaihetta

Fail-fast- ja fail-safe-iteraattorit on ilmainen Java Academy-oppitunti CoddyKitissä. Tämä on oppitunti 4/4. Voit lukea koko oppitunnin alta ilmaiseksi ja harjoitella sen jälkeen käytännössä selaimessa sisäänrakennetulla koodieditorilla ja ympäri vuorokauden käytettävissä olevan tekoälytuutorin avulla. Oppitunti kuuluu Java Academy-oppimispolkuun, ja edistymisesi synkronoituu verkon ja CoddyKit-sovelluksen välillä. Java Academy-kurssilla on yhteensä 4 oppituntia.

Fail-fast- ja fail-safe-iteraattorit

Java-iteraattorit luokitellaan niiden toiminnan perusteella tilanteessa, jossa taustalla olevaa kokoelmaa muokataan läpikäynnin aikana: fail-fast-iteraattorit ilmoittavat virheestä heti, kun taas fail-safe-iteraattorit jatkavat toimintaansa tilannevedoksen perusteella.

Fail-fast-iteraattorit

Useimmat Javan vakiokokoelmat (ArrayList, HashMap, TreeSet) käyttävät fail-fast-iteraattoreita. Ne seuraavat modCount-arvoa eli muutoslaskuria. Rakenteellinen muutos läpikäynnin aikana aiheuttaa ConcurrentModificationException-poikkeuksen.

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.

Miksi fail-fast on olemassa

Fail-fast-toiminta auttaa virheenkorjauksessa: se paljastaa virheet heti sen sijaan, että läpikäynti jatkuisi vioittuneen tilan kanssa. Vaihtoehtona oleva hiljainen tietojen vioittuminen olisi huomattavasti vaikeampi korjata.

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

Turvallinen poistaminen: Iterator.remove()

Ainoa turvallinen muutos Iterator-pohjaisen läpikäynnin aikana on Iterator.remove(): se poistaa viimeksi palautetun alkion ja päivittää modCount-arvon.

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: nykyaikainen vaihtoehto

Java 8 lisäsi Collection-rajapintaan menetelmän removeIf(Predicate), joka tarjoaa selkeämmän tavan ehdon täyttävien alkioiden poistamiseen ilman iteraattorin manuaalista hallintaa.

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-iteraattorit: CopyOnWriteArrayList

CopyOnWriteArrayList käyttää fail-safe-iteraattoria. Se käy läpi iteraattorin luomishetkellä otetun tilannevedoksen, joten muutokset eivät vaikuta meneillään olevaan läpikäyntiin.

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

ConcurrentHashMap käyttää heikosti yhdenmukaisia iteraattoreita: ne kuvaavat tilaa jossakin vaiheessa iteraattorin luomisen aikana tai sen jälkeen. CME-poikkeusta ei heitetä, mutta iteraattori saattaa nähdä samanaikaiset päivitykset tai olla näkemättä niitä.

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 EI ole fail-safe: se käyttää edelleen fail-fast-iteraattoreita, mutta estää samanaikaisen käytön. Teidän on synkronoitava läpikäynti manuaalisesti.

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

Tilannevedokseen perustuva iteraattorimalli

Luokaa tilannevedos manuaalisesti, jotta voitte käydä muuttuvan kokoelman turvallisesti läpi ilman synkronointia.

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

CopyOnWrite-rakenteen kompromissit

CopyOnWriteArrayList on fail-safe, mutta kirjoitusoperaatiot ovat kalliita. Jokainen muutos luo täydellisen kopion.

  • Sopii: tilanteisiin, joissa luetaan paljon mutta kirjoitetaan harvoin (tapahtumankuuntelijalistat)
  • Ei sovi: usein muuttuviin tai suuriin kokoelmiin

Yhteenveto: fail-fast ja fail-safe

Vertailun yhteenveto:

  • Fail-fast: ArrayList, HashMap, TreeMap – heittävät CME-poikkeuksen muutoksen yhteydessä ja tarkistavat modCount-arvon
  • Fail-safe / heikosti yhdenmukainen: ConcurrentHashMap, CopyOnWriteArrayList – eivät heitä CME-poikkeusta, vaan käyvät läpi tilannevedoksen tai samanaikaisesti muuttuvia tietoja
  • Turvallisia muokkausmenetelmiä ovat Iterator.remove(), removeIf() ja replaceAll()

Käytännössä: vanhentuneiden istuntojen siivoaminen

Vanhentuneet istunnot poistetaan turvallisesti samanaikaisesta listasta käyttämällä tarkoitukseen sopivia malleja.

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

Pikatarkistus

Minkä poikkeuksen aiheuttaa ArrayList-rakenteen muokkaaminen läpikäynnin aikana?

Kertaus: fail-fast- ja fail-safe-iteraattorit

Tärkeimmät asiat:

  • Fail-fast-iteraattorit (ArrayList, HashMap) heittävät ConcurrentModificationException-poikkeuksen samanaikaisen muokkauksen yhteydessä
  • Fail-safe-iteraattorit (CopyOnWriteArrayList, ConcurrentHashMap) sallivat muokkaamisen ilman poikkeusta
  • Iterator.remove() on ainoa turvallinen tapa poistaa alkio fail-fast-läpikäynnin aikana
  • removeIf() on nykyaikainen ja selkeämpi vaihtoehto manuaaliselle Iterator.remove()-kutsulle
  • CopyOnWriteArrayList sopii tilanteisiin, joissa luetaan paljon mutta kirjoitetaan harvoin
  • ConcurrentHashMap on heikosti yhdenmukainen: se ei heitä CME-poikkeusta, mutta saattaa nähdä samanaikaiset muutokset tai olla näkemättä niitä
Aloita maksutta

Opi Java tekoälytuutorin avulla — ilmaiseksi

Kirjoita ja suorita oikeaa koodia selaimessa, saa välitöntä apua tekoälytuutorilta ympäri vuorokauden ja jatka siitä, mihin jäit, verkossa tai sovelluksessa.

Kurssit
104
Oppitunnit
374

Usein kysytyt kysymykset

Onko oppitunti ”Fail-fast- ja fail-safe-iteraattorit” ilmainen?

Kyllä – oppitunnin ”Fail-fast- ja fail-safe-iteraattorit” koko tekstin voi lukea täällä verkossa ilmaiseksi. Jos haluat harjoitella interaktiivisesti sisäänrakennetulla koodieditorilla ja ympäri vuorokauden käytettävissä olevan tekoälytuutorin avulla sekä avata koko Java Academy-kurssin, päivitä CoddyKit PROhon. Java Academy-kurssilla on yhteensä 4 oppituntia.

Mitä opin oppitunnilla ”Fail-fast- ja fail-safe-iteraattorit”?

Ymmärrä ConcurrentModificationException-poikkeus ja fail-fast-toiminta sekä tiedä, milloin snapshot-iteraattoreita kannattaa käyttää. Harjoittelet Java Academy-aihetta koodilla, jonka suoritat suoraan selaimessa. Ympäri vuorokauden käytettävissä oleva tekoälytuutori vastaa kysymyksiisi oppitunnin aikana.

Tarvitsenko kokemusta aloittaakseni Java Academy-opiskelun?

Aiempi kokemus ei ole tarpeen. CoddyKitin Java Academy-oppimispolku sopii vasta-alkajista edistyneisiin, joten voit aloittaa tästä tai alusta ja edetä omaan tahtiisi. Tämä on oppitunti 4/4.

Kuinka kauan ”Fail-fast- ja fail-safe-iteraattorit”-oppitunnin suorittaminen kestää?

Useimmat CoddyKitin oppitunnit kestävät noin 5–10 minuuttia. Jokainen oppitunti on lyhyt ja interaktiivinen, joten edistyt tasaisesti ja voit jatkaa siitä, mihin jäit – sekä verkossa että sovelluksessa.

Voinko kirjoittaa ja suorittaa koodia tällä Java Academy-oppitunnilla?

Kyllä. Jokainen Java Academy-oppitunti sisältää sisäänrakennetun koodieditorin, joten voit kirjoittaa ja suorittaa oikeaa koodia suoraan selaimessa ja saada välitöntä palautetta tekoälyltä – paikallista asennusta ei tarvita.

Kaikki tämän kurssin oppitunnit

  1. Iterable- ja Iterator-sopimukset
  2. Mukautetun iteraattorin toteuttaminen
  3. ListIterator ja kaksisuuntainen läpikäynti
  4. Fail-fast- ja fail-safe-iteraattorit
← Takaisin: Java Academy