快速失败(Fail-Fast)是迭代器检测到集合被结构性修改后立即抛出ConcurrentModificationException的机制;其原理是比对modCount与expectedModCount两个计数器,仅对add/remove等改变结构的操作敏感,安全修改需用迭代器remove、批量删除或Fail-Safe集合。

快速失败(Fail-Fast)不是“程序崩了”,而是迭代器主动发现集合被非法修改后,立刻抛出 ConcurrentModificationException 来中止遍历——它是一种检测机制,目的不是阻止修改,而是帮你尽早发现逻辑错误。
为什么一改就报错?关键在两个计数器
以 ArrayList 为例:
- modCount:集合自身的修改计数器,每次 add/remove 等结构性操作都会自增
- expectedModCount:迭代器创建时“拍下的快照”,等于当时的 modCount
- 每次调用
next()或hasNext()前,都会检查二者是否一致;不一致就直接抛异常
哪些操作会触发快速失败?
注意:只对结构性修改敏感,即改变集合大小或元素位置的操作:
将后端 Java 接口文档转换为前端 TypeScript 类型定义(interface / type)。输入:飞书文档、Spring Boot 接口文档、Swagger/OpenAPI、手写 API 文档等。输出:包含请求参数与响应结果的 .md 文件,含完整 JSDoc 注释。触发关键词:Java 转 TS、接口转换、生成前端类型、接口文档转 TypeScript、DTO/VO/BO 转 interface、后端文档转前端类型、生成接口类型、类型定义生成。
- ✅
list.add()、list.remove()、list.clear() - ✅ 多线程中其他线程调用了上述方法
- ❌
list.set(2, "new")(仅替换值,不改变结构) - ❌ 对 Map 的 value 修改(如
map.put(key, newValue)是结构性修改,但map.get(key).setValue(...)不是)
怎么安全地边遍历边修改?
不能靠 try-catch 捕获 ConcurrentModificationException 来“兜底”,那是在掩盖问题。正确做法有三种:
-
用迭代器自己的 remove() 方法:它会同步更新 expectedModCount,例如:
Iterator<String> it = list.iterator();<br>while(it.hasNext()) {<br> if (it.next().equals("target")) it.remove(); // 安全</br>} -
收集待删元素,遍历完再批量删:适合需判断多个条件的场景
List<String> toRemove = new ArrayList<>();<br>for (String s : list) { if (s.startsWith("A")) toRemove.add(s); }<br>list.removeAll(toRemove); -
换用 Fail-Safe 集合:如
CopyOnWriteArrayList,它内部用快照机制,遍历时允许修改,但注意写操作开销大、读取不反映最新变更
别踩这些常见坑
快速失败不是线程安全的保证,也不是运行时必须触发的铁律:
- 官方明确说明:fail-fast 行为不保证 100% 发生,仅尽力检测;不要依赖它做业务逻辑分支
- 增强 for 循环(
for (e : list))底层就是 Iterator,同样受 fail-fast 约束 - 使用
list.get(i)下标遍历不会触发该机制——但它不安全:可能越界、无法动态跳过元素、并发下仍可能读到脏数据

















