Java 中如何处理集合的并发修改异常
单线程遍历集合时直接调用移除方法会因修改计数与预期计数不一致引发并发修改异常。解决方案包括使用迭代器的移除方法安全删除、Java8的按条件移除批量过滤、多线程场景选用写时复制数组等并发集合,或暂存待删列表后统一删除。
ConcurrentModificationException 可不是多线程的专利——单线程遍历集合时,如果你手痒直接调用 list.remove() 改动了集合结构,它照样会跳出来。背后的核心机制是 fail-fast:每次结构修改(比如 add、remove)都会更新 modCount,而迭代器内部记录着自己的 expectedModCount,两者一旦对不上,二话不说就抛异常,防止数据错乱甚至数组越界。那么,怎么避开这个坑?下面几种解法各有适用场景,挑对的那款就好。
用 Iterator.remove() 安全删除
这是最基础也最稳妥的单线程解法。关键在于,Iterator.remove() 和集合自带的 remove() 完全是两码事——它会在内部同步更新状态,所以不会触发校验失败。
- 必须紧跟在
next()调用之后、下一次next()之前调用,否则会抛IllegalStateException。 - 增强 for 循环里用不了,因为拿不到
Iterator对象,得手动写迭代器循环。 - 写法示例:
it.remove()✅;list.remove(item)❌
优先用 removeIf() 批量过滤
Ja va 8 之后,官方推荐用 removeIf(),一句话搞定,底层走的也是安全的迭代逻辑,比手写 Iterator 更简洁、更不容易出错。
- 适用于
List、Set等实现了该方法的集合。 - 写法:
list.removeIf(x -> x.startsWith("A")),语义清晰,一行解决。 - 比起“先收集再删”,它更省内存,也不用担心索引偏移的问题。
多线程场景选对并发集合
别一根筋用 synchronized 硬锁,优先考虑专为并发设计的集合,效果和性能都好得多。
CopyOnWriteArrayList:适合读多写少的场景(比如监听器列表),遍历时可以安全增删,但每次写都会复制整个数组,写频繁时 GC 压力不小。ConcurrentHashMap:高并发 Map 操作的首选,它的keySet()、values()迭代不会抛 CME。注意像computeIfAbsent这样的方法要按文档使用。- 尽量避开
Collections.synchronizedList:它只是给每个方法加了锁,遍历时你仍得手动同步整个代码块,容易漏掉,而且性能一般。
复杂逻辑用暂存+统一删除
如果判断条件太复杂,用不了 removeIf,又不想换集合类型,那这个兜底方案就很实用。
- 新建一个临时集合(比如
ArrayList或HashSet),用来存放待删除的元素。 - 遍历原集合时只做判断和收集,千万别去动原集合的结构。
- 遍历结束后,统一调用
list.removeAll(toRemove),或者逐个remove()。 - 小技巧:如果原集合是
HashSet,用HashSet来存待删项,能让removeAll跑得更快。
Postman是一套面向API全生命周期的开发与测试平台,可在Mac上发送和调试多种协议请求,管理集合、环境及认证信息,并完成脚本测试、模拟服务、监控、文档发布和团队协作。它适合开发、测试及运维人员使用,提供个人免费方案。
Docker Desktop 是 Docker 官方为 Mac 提供的容器开发环境,集成 Docker Engine、命令行工具、Compose、Build、Kubernetes 与图形化管理界面。
Android Studio 是 Google 面向安卓应用开发提供的官方集成开发环境,基于 IntelliJ IDEA 构建。它整合智能代码编辑、Gradle 构建、界面预览、设备模拟、性能分析、调试测试及 APK 分析等工具。
PixPix 是面向品牌、卖家和内容团队的在线 AI 图片视频创作平台,提供图视频 Agent、无限画布、图片生成、视频生成、自然语言编辑及商品套图、产品精修和带货视频等电商工具。
Postman 是面向 API 全生命周期的开发与协作平台。Windows 桌面版可用于构建和发送 HTTP、GraphQL、gRPC、WebSocket、MQTT 等请求,并提供环境变量、集合、脚本测试、模拟服务、监控、文档及团队工作区等
Docker Desktop 为 Windows 开发者提供集成式容器环境,内置 Docker Engine、命令行工具、Compose、Build 与 Kubernetes 等组件,可在本机完成镜像构建、容器运行、日志查看和资源管理。













