ConcurrentModificationException是单线程下“边迭代边修改”触发的fail-fast机制,非多线程并发导致;底层通过modCount与expectedModCount比对检测结构变更,不一致则抛异常。

为什么遍历集合时修改会抛出ConcurrentModificationException
这个异常不是因为多线程并发导致的(虽然名字容易误导),而是ArrayList、HashMap等非线程安全集合在单线程中「边迭代边修改」触发了快速失败(fail-fast)机制。底层靠modCount计数器检测结构变更:每次调用add()、remove()等方法都会递增它,而迭代器在next()或hasNext()时会校验当前modCount是否与初始化时一致,不一致就直接抛异常。
常见错误现象:
- 用
for-each循环遍历ArrayList,内部调用iterator(),然后在循环体里调用list.remove(obj) - 用
Iterator遍历,但调用的是集合自身的remove()而非iterator.remove() - 多个线程共用同一个
ArrayList,一个在读(遍历),一个在写(增删)
单线程下安全删除元素的正确方式
核心原则:必须通过迭代器自身提供的remove()方法来删除当前元素,它会同步更新expectedModCount,避免校验失败。
Listlist = new ArrayList<>(Arrays.asList("a", "b", "c", "d")); Iterator it = list.iterator(); while (it.hasNext()) { String s = it.next(); if ("b".equals(s)) { it.remove(); // ✅ 正确:调用迭代器的 remove() } }
其他可行方案:
立即学习“Java免费学习笔记(深入)”;
基于Intranet/Internet 的Web下的办公自动化系统,采用了当今最先进的PHP技术,是综合大量用户的需求,经过充分的用户论证的基础上开发出来的,独特的即时信息、短信、电子邮件系统、完善的工作流、数据库安全备份等功能使得信息在企业内部传递效率极大提高,信息传递过程中耗费降到最低。办公人员得以从繁杂的日常办公事务处理中解放出来,参与更多的富于思考性和创造性的工作。系统力求突出体系结构简明
- 收集待删元素,遍历结束后统一调用
list.removeAll(toRemove) - 使用
removeIf()(Java 8+):list.removeIf(s -> "b".equals(s)) - 倒序索引遍历(仅适用于
ArrayList等支持随机访问的集合):for (int i = list.size() - 1; i >= 0; i--) { if (needRemove(list.get(i))) list.remove(i); }
多线程环境下的替代集合选择
如果确实需要多线程并发读写且遍历时不报错,不能靠加锁硬扛(锁住整个遍历过程性能差、易死锁),应换用线程安全的集合实现:
-
CopyOnWriteArrayList:适合读多写少场景;遍历时操作的是快照,写操作会复制整个数组,Iterator不支持remove() -
ConcurrentHashMap:迭代其keySet()、values()或entrySet()不会抛ConcurrentModificationException,但结果可能不反映实时状态(弱一致性) -
BlockingQueue子类(如LinkedBlockingQueue):适用于生产者-消费者模型,提供线程安全的增删操作
注意:Vector和Hashtable虽是线程安全的,但它们的迭代器仍是fail-fast的,同样会抛该异常。
调试时如何快速定位问题源头
异常堆栈通常只显示在next()或hasNext()处抛出,但真正引发modCount变化的位置往往在别处。关键看异常信息中的「at」行和上层调用链:
- 检查堆栈中是否有
remove()、add()、clear()等结构修改方法出现在迭代逻辑附近 - 确认是否在Lambda表达式、Stream操作(如
forEach)中隐式调用了集合修改方法 - 留意匿名内部类或监听器回调里是否意外修改了外部集合
最容易被忽略的一点:即使你没显式写remove(),某些工具方法(比如Collections.synchronizedList()包装后返回的代理对象,其iterator()仍不支持并发修改)也会导致相同行为——它只是同步了单个方法,没解决迭代器一致性问题。









