969 字
5 分钟
集合遍历与线程安全:fail-fast、迭代器删除与快照

集合上的单次方法调用与“遍历、检查、修改”这类组合操作是不同的问题。面试不只要回答哪个类线程安全,还要知道安全保证覆盖多大的操作范围。

什么是 fail-fast?#

ArrayList、HashMap 等普通集合的迭代器通常记录创建时的结构修改计数,遍历过程中发现不符合预期的修改,就可能抛 ConcurrentModificationException。

它是尽力发现错误的机制,不是线程安全机制,也不保证发现所有并发修改。单线程遍历时直接修改集合,同样可能触发这个异常。

List<String> names = new ArrayList<>(Arrays.asList("A", "B", "C"));
Iterator<String> it = names.iterator();
while (it.hasNext()) {
if ("B".equals(it.next())) {
it.remove();
}
}

需要删除元素时,可以使用支持删除的 Iterator.remove,或使用集合支持的 removeIf。Iterator.remove 必须符合调用顺序,一般在 next 后调用一次;它并没有让整个遍历变成多线程安全操作。

增强 for 为什么不能随意 remove?#

对普通 Iterable 的增强 for 通常使用迭代器。循环里调用 list.remove(...) 会绕过迭代器自己的状态管理,后续迭代可能检测到修改;也可能因遍历提前结束而没有抛异常,所以“这次没报错”不能证明写法正确。

并非所有值变化都是结构修改,例如 ArrayList.set 通常不改变元素数量;但这也不意味着可随意并发调用 set。

三种常见安全方式#

方式核心保证使用边界
synchronizedList 等同步包装对单次方法做同步保护遍历和组合操作通常还要使用同一把锁
CopyOnWriteArrayList写时复制,迭代读取快照写入成本高,迭代不会反映后续修改,迭代器不支持 remove
ConcurrentHashMap支持高并发访问,迭代弱一致迭代不保证看到某个统一时刻的全局快照

“fail-safe”不是集合 API 对这些机制的统一正式分类,复习时直接说快照迭代、弱一致迭代更准确。

线程安全 Map 的组合操作一定安全吗?#

不一定。if (!map.containsKey(key)) map.put(key, value) 包含两个独立操作,另一个线程可以插入在它们之间。

使用 ConcurrentHashMap 时,可以通过 putIfAbsent、compute 等提供的原子操作表达对应逻辑。回调应简短,不放耗时远程调用或复杂的相互更新,避免竞争和阻塞。

Arrays.asList 与不可修改列表#

Arrays.asList 返回由原数组支持的固定大小列表,通常支持 set,但 add/remove 会抛 UnsupportedOperationException;修改数组与修改列表元素会相互影响。把基本类型数组传进去,例如 int[],得到的通常是包含一个数组元素的列表,不是整数逐项组成的列表。

List.of(JDK 9+)返回不可修改列表,并拒绝 null 元素。Collections.unmodifiableList 返回不可修改视图,原列表通过其他引用修改后,视图可能跟着变化。不可修改列表也不保证其中每个对象不可变;需要可修改副本时,可以显式 new ArrayList<>(source)。

面试回答模板#

fail-fast 通过修改计数尽力发现不符合预期的结构修改,可能在单线程或多线程出现,但不提供线程安全保证。普通集合删除优先使用支持的迭代器方法或 removeIf。同步集合通常只保护单次调用,遍历和组合逻辑还要考虑整体同步。CopyOnWriteArrayList 读取快照,ConcurrentHashMap 迭代弱一致,不能把它们都理解为实时完整快照。

自测#

  1. 没有 ConcurrentModificationException 能证明并发修改安全吗?不能。
  2. synchronizedList 的 for-each 是否自动在整个遍历期间加锁?不会。
  3. ConcurrentHashMap 的 containsKey 加 put 是一个原子操作吗?不是,应根据意图使用原子 API。
集合遍历与线程安全:fail-fast、迭代器删除与快照
https://blog.xqcherry.top/posts/interview/java/collection-iteration-and-safety/
作者
xqcherry
发布于
2026-10-12
许可协议
CC BY-NC-SA 4.0