java并发修改异常需分单线程与多线程场景处理:单线程用iterator.remove()、removeif()或批量删除;多线程用concurrenthashmap、copyonwritearraylist或加锁;禁用collections.synchronizedlist后直接foreach。

Java 中处理并发修改异常(ConcurrentModificationException),关键在于区分场景:是单线程遍历时误改集合,还是多线程真实并发冲突。处理方式完全不同,选错方案反而引入性能瓶颈或隐藏 bug。
单线程下边遍历边删元素
这是最常见也最容易被忽略的场景。foreach 循环本质就是 Iterator,一旦在循环体里调用 list.remove() 或 map.remove(),就会触发 fail-fast 检查,立即抛异常。
- ✅ 推荐用
Iterator.remove():它会同步更新内部计数器,安全删除当前元素 - ✅ Java 8+ 用
removeIf():一行代码完成过滤,语义清晰且线程安全(仅限单线程) - ✅ 先收集再批量删:遍历时把待删元素加进临时 List,循环结束后调
list.removeAll(toRemove) - ⚠ 避免倒序 for 循环手动删:虽不抛异常,但易漏删或索引越界,可读性差,不推荐
多线程共享集合读写混杂
多个线程同时读、写同一个 ArrayList 或 HashMap,哪怕没用 foreach,只要一个线程在迭代,另一个线程执行了 add/remove,就可能出问题。
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- ✅ 优先换并发集合:
ConcurrentHashMap替代HashMap,CopyOnWriteArrayList替代普通List(注意后者适合读远多于写的场景) - ✅ 加锁保护:对非线程安全集合,所有访问(包括遍历)都必须包裹在
synchronized块中,或用ReentrantLock - ❌ 不要用
Collections.synchronizedList()后直接 foreach:它只保证单个方法原子,遍历仍是多步操作,仍需额外同步
为什么不能靠“试试看”来规避
有些开发者发现偶尔不抛异常就以为没问题——这很危险。fail-fast 不是 100%实时检测,而是依赖 modCount 和 expectedModCount 的比对时机。一次没抛,不代表逻辑正确;一次抛了,说明结构已不一致,后续行为不可信。
- 它不是并发 bug 的根源,而是暴露根源的哨兵
- 修复目标不是“让异常不出现”,而是“让修改与遍历互斥或隔离”
- 日志里反复出现这个异常,往往意味着设计上缺少明确的读写边界
简单判断该用哪种方案
看两件事:
- 代码里有没有
new Thread(...).start()、ExecutorService、异步回调、定时任务?有 → 多线程场景 → 上并发集合或锁 - 是不是纯主线程跑完一个方法,里面用了 foreach + remove?是 → 单线程场景 → 改用
iterator.remove()或removeIf()
不复杂但容易忽略
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










