快速失败与安全失败迭代器
理解 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 copyConcurrentHashMap 迭代器
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 consistentCollections.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 反馈 — 无需本地设置。
此课程中的所有课时
- Iterable 与 Iterator 契约
- 实现自定义迭代器
- ListIterator 与双向遍历
- 快速失败与安全失败迭代器