ConcurrentModificationException的根本原因是fail-fast机制检测到集合被非迭代器方式结构性修改,单线程下调用list.remove()等方法也会触发;必须用iterator.remove()安全删除,或改用CopyOnWriteArrayList等线程安全集合。

Java中Iterator抛ConcurrentModificationException,根本原因是快速失败(fail-fast)机制在检测到集合被意外修改时主动中断遍历,不是因为“多线程并发”本身,而是单线程下用非迭代器方式修改了正在遍历的集合——这是最容易被误解的一点。
为什么单线程也会触发?
每个支持迭代的集合(如ArrayList、HashMap)内部都有一个modCount(修改计数器),记录结构修改次数。迭代器创建时会把当时的modCount复制为自己的expectedModCount。每次调用next()或hasNext()前,都会检查两者是否一致。只要集合被add()、remove()、clear()等方法结构性修改,modCount就会加1,而迭代器的expectedModCount没变,校验失败就抛异常。
常见“单线程踩坑”场景:
- 边遍历
for (String s : list),边调用list.remove(s) - 用
iterator.hasNext()循环,但删除元素时用了list.remove(index)而非iterator.remove() - 多个迭代器共享同一个集合,一个删了,另一个接着遍历
正确删除元素:必须用迭代器自身的remove()
Iterator提供了安全删除方法:仅在调用过next()之后,立刻调用iterator.remove(),它会同步更新expectedModCount,避免异常。
立即学习“Java免费学习笔记(深入)”;
示例:
Iteratorit = list.iterator(); while (it.hasNext()) { String s = it.next(); if ("target".equals(s)) { it.remove(); // ✅ 安全删除 } }
其他规避方案(按推荐顺序)
-
使用支持并发的集合:如
CopyOnWriteArrayList(适合读多写少)、ConcurrentHashMap。它们不依赖modCount,迭代器基于快照,不会抛该异常 -
收集待删元素,遍历结束后统一删:适用于逻辑清晰、内存可接受的场景
ListtoRemove = new ArrayList();
for (String s : list) { if (needRemove(s)) toRemove.add(s); }
list.removeAll(toRemove); -
倒序索引遍历(仅限List):用
for (int i = list.size()-1; i >= 0; i--),删除不影响后续索引,但需注意业务逻辑是否允许乱序处理 - 用Stream过滤(Java 8+):list = list.stream().filter(s -> !s.equals("target")).collect(Collectors.toList()); —— 创建新集合,原集合不变
多线程下的特别注意
即使用了iterator.remove(),在多线程环境下仍不安全:迭代器本身不是线程安全的。两个线程同时遍历+修改,依然可能出错。此时必须:
- 加外部同步(如
synchronized(list)块内完成整个遍历+修改) - 改用线程安全集合(如
CopyOnWriteArrayList,其迭代器天然支持并发读) - 用
java.util.concurrent包提供的高级工具(如ConcurrentLinkedQueue配合poll()消费)
基本上就这些。核心记住一点:ConcurrentModificationException是集合自我保护的信号,不是bug,而是提醒你——别绕过迭代器直接动集合结构。










