跳到主要内容

迭代器与 fail-fast

问题

Java 迭代器是什么?fail-fast 和 fail-safe 机制有什么区别?

答案

Iterator 接口

IteratorDemo.java
List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));

// 使用 Iterator 遍历
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String item = it.next();
if ("b".equals(item)) {
it.remove(); // 安全删除当前元素
}
}

// 增强 for 循环底层就是 Iterator
for (String item : list) {
System.out.println(item);
}

fail-fast(快速失败)

java.util 包下的集合(ArrayList、HashMap 等)使用 fail-fast 机制。在迭代过程中,如果检测到集合结构被修改(增加/删除元素),立即抛出 ConcurrentModificationException

FailFastDemo.java
List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));

// ❌ 增强 for 循环中修改 → ConcurrentModificationException
for (String item : list) {
if ("b".equals(item)) {
list.remove(item); // 修改了集合结构
}
}

原理:集合内部维护 modCount(修改计数器),每次结构修改时 modCount++。Iterator 创建时记录 expectedModCount = modCount,每次 next() 时检查两者是否一致:

ArrayList.Itr(简化)
private class Itr implements Iterator<E> {
int expectedModCount = modCount; // 创建时记录

public E next() {
checkForComodification(); // 检查是否被修改
// ... 返回元素
}

final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}

public void remove() {
// 通过 Iterator.remove() 删除会同步更新 expectedModCount
ArrayList.this.remove(lastRet);
expectedModCount = modCount; // 保持同步
}
}

fail-safe(安全失败)

java.util.concurrent 包下的集合(CopyOnWriteArrayList、ConcurrentHashMap 等)使用 fail-safe 机制。迭代时操作的是数据的快照或副本,不会抛 ConcurrentModificationException:

FailSafeDemo.java
// CopyOnWriteArrayList:迭代器基于快照
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>(
Arrays.asList("a", "b", "c")
);
for (String item : list) {
list.add("d"); // 不会抛异常,但迭代器看不到新增的 "d"
System.out.println(item);
}

// ConcurrentHashMap:弱一致性迭代器
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.put("a", 1);
map.put("b", 2);
for (Map.Entry<String, Integer> entry : map.entrySet()) {
map.put("c", 3); // 不会抛异常
}

fail-fast vs fail-safe 对比

维度fail-fastfail-safe
所在包java.utiljava.util.concurrent
代表ArrayList, HashMapCopyOnWriteArrayList, ConcurrentHashMap
并发修改抛 ConcurrentModificationException正常运行
数据一致性检测到不一致立即报错弱一致性(可能读到旧数据)
迭代器类型基于原始集合基于快照或弱一致性视图
内存开销无额外开销可能有快照副本开销

常见面试问题

Q1: 如何安全地在遍历中删除集合元素?

答案

List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c", "d"));

// 方式一:Iterator.remove()
Iterator<String> it = list.iterator();
while (it.hasNext()) {
if ("b".equals(it.next())) it.remove();
}

// 方式二:removeIf(JDK 8+,推荐)
list.removeIf(item -> "b".equals(item));

// 方式三:使用并发集合
CopyOnWriteArrayList<String> safeList = new CopyOnWriteArrayList<>(list);
for (String item : safeList) {
if ("b".equals(item)) safeList.remove(item);
}

Q2: ConcurrentModificationException 一定是多线程导致的吗?

答案

不一定。单线程中,在增强 for 循环内直接调用集合的 add/remove 方法,也会触发 ConcurrentModificationException。这个异常名字有误导性,本质是"迭代期间检测到结构性修改"。

Q3: Iterable 和 Iterator 的区别?

答案

  • Iterable:表示对象可以被迭代(可以用 for-each),只有一个方法 iterator() 返回 Iterator
  • Iterator:迭代器本身,有 hasNext()next()remove() 方法
// 实现 Iterable 即可使用 for-each
public class Range implements Iterable<Integer> {
private final int start, end;
public Range(int start, int end) { this.start = start; this.end = end; }

@Override
public Iterator<Integer> iterator() {
return new Iterator<>() {
private int current = start;
public boolean hasNext() { return current < end; }
public Integer next() { return current++; }
};
}
}

for (int i : new Range(1, 5)) {
System.out.println(i); // 1, 2, 3, 4
}

相关链接