ConcurrentModificationException 不仅多线程出现,单线程遍历时直接修改集合也会触发,因 fail-fast 机制校验 modCount 与 expectedModCount 不一致;安全解法包括 Iterator.remove()、removeIf()、并发集合(如 CopyOnWriteArrayList、ConcurrentHashMap)及暂存后统一删除。

Java 中的 ConcurrentModificationException 并不只在多线程下出现,单线程遍历时直接修改集合结构(比如用 list.remove())同样会触发。根本原因是集合的 fail-fast 机制:每次结构修改(add/remove)都会更新 modCount,而迭代器持有自己的 expectedModCount;两者不一致时立刻抛异常,防止数据错乱或越界。
用 Iterator.remove() 安全删除
这是最基础、最稳妥的单线程解法。它和集合自身的 remove() 完全不同——Iterator.remove() 会同步更新内部状态,避免校验失败。
- 必须在调用
next()之后、下一次next()之前调用,否则抛IllegalStateException - 不能在增强 for 循环里用,因为拿不到
Iterator对象 - 示例:
it.remove()✅;list.remove(item)❌
优先用 removeIf() 批量过滤
Java 8+ 推荐写法,语义清晰、一行解决、底层仍基于安全的迭代逻辑,比手写 Iterator 更少出错。
- 适用于
List、Set等支持该方法的集合 - 写法简洁:
list.removeIf(x -> x.startsWith("A")) - 比“先收集再删”更省内存,也不涉及索引偏移问题
多线程场景选对并发集合
别硬套 synchronized,优先用专为并发设计的集合:
立即学习“Java免费学习笔记(深入)”;
-
CopyOnWriteArrayList:适合读远多于写的场景(如监听器列表),遍历时可安全增删,但每次写都复制数组,写频繁时 GC 压力大 -
ConcurrentHashMap:高并发 Map 操作首选,其keySet()、values()迭代不会抛 CME,注意computeIfAbsent等方法需按文档使用 - 避免用
Collections.synchronizedList:只是给方法加锁,遍历时仍需手动同步整个块,易遗漏且性能差
复杂逻辑用暂存+统一删除
当判断条件复杂、无法用 removeIf,又不想引入新集合类型时,这是一种通用兜底策略。
- 新建临时集合(如
ArrayList或HashSet)存放待删元素 - 遍历原集合只做判断和收集,绝不修改原集合
- 遍历结束后统一调用
list.removeAll(toRemove)或逐个remove() - 小技巧:若原集合是
HashSet,用HashSet存待删项可提升removeAll效率


















