ConcurrentModificationException 应避免而非捕获,它是线程不安全迭代+修改的 fail-fast 警示;正确做法是选用 CopyOnWriteArrayList、同步包装类配合显式锁或不可变集合等线程安全方案。

Java 中 List 的并发修改异常(ConcurrentModificationException)通常不是“被正确处理”的问题,而是应当**避免触发**——它本质是快速失败(fail-fast)机制的警示信号,说明代码存在线程不安全的迭代+修改行为。真正要做的,是选用线程安全的集合或加锁策略,而非捕获并“处理”这个异常。
为什么不能靠 try-catch 来解决
ConcurrentModificationException 不是业务异常,而是 JVM 对结构性修改(如 add/remove)与迭代器遍历同时发生的主动中断。捕获它无法恢复数据一致性,也不能保证下次不发生——只是掩盖了并发缺陷。
- 它不表示“偶尔出错”,而代表当前操作逻辑在多线程下天然不成立
- 即使 catch 后重试,仍可能因竞态条件导致漏处理、重复处理或无限循环
- JDK 文档明确指出:该异常用于检测 bug,不应作为控制流的一部分
推荐替代方案:按场景选线程安全实现
根据读写比例、实时性要求和是否允许弱一致性,选择合适方案:
分析 Java Spring Boot 后端 Controller 代码,自动生成 Markdown 格式的接口文档。当用户说“生成 Java 接口文档”、“生成 API 文档”、“从 Spring 代码生成文档”时触发。
- CopyOnWriteArrayList:适合读多写少(如监听器列表、配置快照)。每次写操作复制整个数组,迭代器基于创建时的快照,完全无锁且不会抛 CME。但写操作开销大,不适用于高频修改场景
- Vector(已基本淘汰):所有方法加 synchronized,性能差且仅提供粗粒度锁;不推荐新代码使用
-
同步包装类:
Collections.synchronizedList(new ArrayList())提供手动加锁能力,但迭代时必须显式同步:
错误写法:for (E e : list) {...}(内部用 iterator,仍会 CME)
正确写法:synchronized(list) { for (E e : list) {...} } -
显式锁 + 普通 ArrayList:用
ReentrantLock或 synchronized 控制读写临界区,灵活性高,但需自行保证所有访问路径受控
更优思路:避免共享可变状态
比选集合更重要的是设计层面规避竞争:
- 用不可变集合(如
ImmutableListfrom Guava 或 JDK 10+List.of())传递数据,写操作生成新副本 - 将 List 封装在 Actor 模型或单线程事件循环中(如 Netty ChannelHandler),天然串行化访问
- 拆分职责:读取用缓存(如 Caffeine),写入走队列(如 BlockingQueue),消费者单线程更新 List
调试技巧:快速定位 CME 根源
若已在测试中复现 CME,不要只看堆栈顶层:
- 检查异常抛出位置附近的
for-each或iterator.next(),确认是否有其他线程正在调用add()/remove() - 注意隐藏修改:
list.clear()、list.retainAll()等也属于结构性修改 - 启用 JVM 参数
-XX:+UnlockDiagnosticVMOptions -XX:+TraceClassLoadingPreorder辅助分析(非必需),但最有效的是用 IDE 断点+线程视图观察竞态时刻
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










