concurrentmodificationexception 是遍历中非法修改集合结构触发的 fail-fast 报警,排查重点是定位遍历过程中直接修改集合结构的代码;单线程应使用 iterator.remove()、removeif() 等安全方式,多线程需选用 copyonwritearraylist 等线程安全集合或加锁。

ConcurrentModificationException 不是并发问题,而是遍历集合时结构被非法修改触发的 fail-fast 报警。排查重点不在“有没有多线程”,而在“谁在遍历过程中动了集合的结构”。
一眼定位问题代码位置
异常堆栈通常指向 iterator.next() 或 hasNext() 调用行,但真正出错的是这之前某次直接调用 list.remove()、set.add() 或 map.put() 的地方。注意检查:
- for-each 循环体内是否写了集合自身的 remove/add/clear
- Iterator 遍历中是否绕过 it.remove(),改用 list.remove()
- 循环内是否隐式调用了其他方法,而那些方法内部又修改了该集合
- 是否在 Lambda 表达式(如 stream().forEach())里修改了外部集合
单线程场景下的安全删除方式
只要不跨线程,问题本质是操作契约错误,修复不难:
- 用 Iterator.remove():它会同步更新内部状态,避免 modCount 不一致
- 用 Collection.removeIf()(Java 8+):语义明确,内部已封装安全逻辑,推荐用于条件删除
- 倒序 for 循环 + 索引删除:从 size-1 开始递减,删完不影响前面元素索引,不走迭代器路径
- 先收集待删元素,再统一 removeAll():适合删除条件复杂或需多次判断的场景
多线程环境的应对策略
若确认存在多个线程同时读写同一集合,不能只靠规避写法,要升级数据结构或控制访问:
- 读多写少 → 用 CopyOnWriteArrayList 或 ConcurrentHashMap,它们天然支持遍历时安全修改
- 读写均衡 → 加 synchronized 块,确保迭代与修改串行执行
- 逻辑可拆分 → 改用不可变集合(如 ImmutableList),每次修改生成新实例,彻底避开结构变更问题
预防比修复更重要
日常编码中养成几个习惯能大幅降低踩坑概率:
- for-each 循环体里禁止出现任何集合结构修改语句
- 看到 iterator.next() 就下意识检查下一行是不是 list.remove()
- 工具类或服务方法接收集合参数时,明确它是只读视图还是允许修改,必要时做 defensive copy
- 单元测试覆盖遍历+删除场景,尤其注意边界情况(如删第一个、最后一个、全删)
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











