Bug排查
Python 可变默认参数陷阱:一个隐蔽的 Bug
Java NullPointerException 深度排查指南
常见 Bug 排查——Python 中 mutable default argument 陷阱(可变默认参数导致的共享状态Bug)
Java Bug排查:ConcurrentModificationException 并发修改异常深度解析
Java 并发 Bug - ConcurrentModificationException 排查与修复
Java 并发 Bug:ArrayList 的 ConcurrentModificationException
Python 可变默认参数陷阱:一次踩坑与彻底修复
本站点使用 MrDoc 构建
-
+
Java 并发 Bug - ConcurrentModificationException 排查与修复
> 分类:Bug 排查 | 语言:Java | 严重程度:🟡 中  ## Bug 现象 在多线程或单线程遍历 `ArrayList` 时,调用 `list.remove()` 后抛出: ``` java.util.ConcurrentModificationException at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909) at java.util.ArrayList$Itr.next(ArrayList.java:859) ``` **典型场景**:遍历集合时删除元素 — 无论是 for-each 循环还是 `forEach` Lambda,都可能触发。 ## 复现步骤 1. 创建一个 `ArrayList<String>` 并添加若干元素 2. 使用 for-each 循环遍历 3. 在循环体内调用 `list.remove(item)` 4. 当删除元素后迭代器继续调用 `next()`,抛出 `ConcurrentModificationException` ```java List<String> list = new ArrayList<>(Arrays.asList("a", "remove", "b", "c")); for (String item : list) { if ("remove".equals(item)) { list.remove(item); // 💥 Boom! } } ```  ## 根因分析 `ArrayList` 的迭代器是 **fail-fast** 设计: - 内部维护 `modCount`(集合结构修改次数)和 `expectedModCount`(迭代器创建时的快照) - 每次 `next()` 调用前,`checkForComodification()` 比较两者 - `list.remove()` 修改了 `modCount`,但迭代器的 `expectedModCount` 未更新 - 两者不一致 → 抛出异常 **多线程场景更危险**:线程 A 遍历,线程 B 同时修改,A 的迭代器完全无感知 B 的修改。 ## 修复方案  ### 方案 1:使用 Iterator.remove() ```java Iterator<String> it = list.iterator(); while (it.hasNext()) { if ("remove".equals(it.next())) { it.remove(); // ✅ 安全:迭代器感知到修改 } } ``` ### 方案 2:使用 CopyOnWriteArrayList ```java List<String> list = new CopyOnWriteArrayList<>(Arrays.asList("a", "remove", "b")); for (String item : list) { if ("remove".equals(item)) { list.remove(item); // ✅ 安全:遍历的是快照 } } ``` ### 方案 3:JDK 8+ Collection.removeIf() ```java list.removeIf("remove"::equals); // ✅ 内部使用迭代器 ``` ### 方案 4:倒序遍历 + remove(仅单线程) ```java for (int i = list.size() - 1; i >= 0; i--) { if ("remove".equals(list.get(i))) { list.remove(i); // ✅ 倒序不影响后续索引 } } ``` ## 预防措施 | 策略 | 适用场景 | 备注 | |------|----------|------| | `Iterator.remove()` | 单线程遍历删除 | 最通用,所有 Collection 支持 | | `removeIf()` | 单线程条件删除 | JDK 8+,代码最简洁 | | `CopyOnWriteArrayList` | 多线程读多写少 | 写操作开销大(复制整个数组) | | `ConcurrentHashMap` | 多线程 Key-Value | 遍历弱一致性,不抛 CME | | 倒序索引遍历 | 单线程 List | 仅适用于按索引访问的 List | **核心原则**:**永远不要在 for-each / forEach 中直接调用集合的 `remove()` / `add()` 方法**。 ## 扩展阅读 - [ArrayList 源码 - Itr 内部类](https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/java/util/ArrayList.java) - [Java Fail-Fast vs Fail-Safe 迭代器](https://docs.oracle.com/javase/8/docs/api/java/util/ConcurrentModificationException.html) - [CopyOnWriteArrayList 原理与适用场景](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CopyOnWriteArrayList.html)
admin
2026年5月17日 01:10
转发文档
收藏文档
上一篇
下一篇
手机扫码
复制链接
手机扫一扫转发分享
复制链接
Markdown文件
PDF文档(打印)
分享
链接
类型
密码
更新密码