0Pricing
Java Academy · 课时

快速失败与安全失败迭代器

理解 ConcurrentModificationException 和快速失败行为,以及何时使用快照迭代器

快速失败与安全失败迭代器 是 CoddyKit 上的免费 Java Academy 课时。 这是第 4 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Java Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Java Academy 课程共包含 4 节课。

快速失败迭代器与安全迭代器

Java 迭代器根据底层集合在遍历期间被修改时的行为进行分类:快速失败迭代器会立即抛出异常;安全迭代器则基于快照继续遍历。

快速失败迭代器

大多数标准 Java 集合(ArrayList、HashMap、TreeSet)都使用快速失败迭代器。它们会跟踪 modCount,即修改计数器。在遍历期间进行任何结构性修改,都会触发 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.

快速失败机制存在的原因

快速失败行为是一种调试辅助机制:它会立即暴露错误,而不是让遍历在状态已损坏的情况下继续进行。相比之下,静默损坏更难调试。

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

安全删除:Iterator.remove()

基于迭代器遍历时,唯一安全的修改方式是 Iterator.remove()——它会删除上一次返回的元素并更新 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:现代替代方案

Java 8 为 Collection 添加了 removeIf(Predicate),无需手动管理迭代器,就能以更简洁的方式删除符合条件的元素。

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

安全迭代器:CopyOnWriteArrayList

CopyOnWriteArrayList 使用安全迭代器。迭代器会遍历创建时获取的快照,后续修改不会影响正在进行的遍历。

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 迭代器

ConcurrentHashMap 使用弱一致性迭代器:它们反映迭代器创建期间或创建之后某个时刻的状态,不会抛出 CME,并且可能看到也可能看不到并发更新。

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 不是安全迭代器——它仍然使用快速失败迭代器,但会阻塞并发访问。遍历期间必须手动进行同步。

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

快照迭代器模式

手动创建快照,即可在不进行同步的情况下安全地遍历可变集合。

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 的取舍

CopyOnWriteArrayList 是安全的,但写入成本很高。每次修改都会创建一个完整副本。

  • 适合:读取多、写入少的场景(事件监听器列表)
  • 不适合:频繁修改或集合规模较大的场景

总结:快速失败与安全迭代器

对比总结:

  • 快速失败:ArrayList、HashMap、TreeMap——修改时抛出 CME,并检查 modCount
  • 安全迭代器/弱一致性:ConcurrentHashMap、CopyOnWriteArrayList——不会抛出 CME,遍历快照或并发数据
  • 安全的修改方法:Iterator.remove()、removeIf()、replaceAll()

实践:清理过期会话

使用恰当的模式,从并发列表中安全地删除过期会话。

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

快速检查

在遍历 ArrayList 时对其进行修改,会抛出什么异常?

回顾:快速失败与安全迭代器

要点:

  • 快速失败迭代器(ArrayList、HashMap)在发生并发修改时会抛出 ConcurrentModificationException
  • 安全迭代器(CopyOnWriteArrayList、ConcurrentHashMap)允许修改而不会抛出异常
  • 在快速失败遍历期间,Iterator.remove() 是唯一安全的删除方式
  • removeIf() 是手动使用 Iterator.remove() 的现代且更简洁的替代方案
  • CopyOnWriteArrayList:适合读取多、写入少的场景
  • ConcurrentHashMap:具有弱一致性——不会抛出 CME,并且可能看到也可能看不到并发修改

常见问题解答

「快速失败与安全失败迭代器」课时是免费的吗?

是的 — 「快速失败与安全失败迭代器」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Java Academy 课程的其余内容,请升级到 CoddyKit PRO。 Java Academy 课程共包含 4 节课。

「快速失败与安全失败迭代器」这节课中我会学到什么?

理解 ConcurrentModificationException 和快速失败行为,以及何时使用快照迭代器 你通过在浏览器中直接运行的动手代码来练习 Java Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 Java Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 Java Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 4 节课,共 4 节。

「快速失败与安全失败迭代器」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 Java Academy 课中编写并运行代码吗?

能。每节 Java Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. Iterable 与 Iterator 契约
  2. 实现自定义迭代器
  3. ListIterator 与双向遍历
  4. 快速失败与安全失败迭代器
← 返回 Java Academy