遍历集合时抛 ConcurrentModificationException 是因迭代器检测到 modCount 与 expectedModCount 不一致;安全删除方式有:①用 Iterator.remove();②收集后批量移除;③倒序索引遍历;多线程须换线程安全集合或加锁。

为什么遍历集合时会抛 ConcurrentModificationException
Java 的 ArrayList、HashMap 等非线程安全集合,在迭代器遍历时会检查 modCount(修改计数)是否与创建迭代器时的 expectedModCount 一致。只要在遍历过程中有其他线程或同一线程调用 add()、remove()、clear() 等结构性修改方法,就会触发该异常。
遍历时安全删除元素的三种实操方式
核心原则:别在 for-each 或普通 for (int i = 0; ...) 循环里直接调用集合的 remove() 方法。
- 用迭代器自身的
remove()方法 —— 唯一被设计为“边遍历边删”的合法方式:Iterator<String> it = list.iterator(); while (it.hasNext()) { String s = it.next(); if (s.startsWith("a")) it.remove(); // ✅ 安全 } - 收集待删元素,遍历结束后批量移除:
List<String> toRemove = new ArrayList<>(); for (String s : list) { if (s.length() > 5) toRemove.add(s); } list.removeAll(toRemove); // ✅ 避开迭代过程中的修改 - 倒序索引遍历(仅适用于
ArrayList等支持随机访问的集合):for (int i = list.size() - 1; i >= 0; i--) { if (list.get(i).isEmpty()) list.remove(i); // ✅ 索引不会因前面元素删除而错位 }
多线程环境下必须换集合类型
单线程里用 Iterator.remove() 能解决问题,但多线程下仍可能因其他线程并发修改而报错。这时不能靠“技巧”规避,必须换线程安全实现:
- 读多写少 → 用
Collections.unmodifiableList()+ 外部同步,或改用CopyOnWriteArrayList(注意:写操作开销大,适合迭代远多于修改的场景) - 读写均衡 → 用
ConcurrentHashMap(注意它不保证遍历时的强一致性,但不会抛ConcurrentModificationException) - 需要强一致性 + 高并发 → 手动加锁(如
synchronized(list)),但要小心死锁和性能瓶颈
Stream API 的 filter() 不等于线程安全
有人误以为用 list.stream().filter(...).collect(...) 就能绕过并发问题 —— 实际上它只是生成新集合,并不改变原集合结构,所以不会触发 ConcurrentModificationException;但它**不解决并发修改本身**:如果另一线程正在往原集合里 add 元素,stream 过程中看到的数据仍是不一致的(可能漏掉新元素,也可能因扩容失败而抛 ArrayIndexOutOfBoundsException)。
真正关键的不是“怎么遍历不报错”,而是“谁在什么时候改了这个集合”。很多线上问题最后都卡在没理清修改源头 —— 比如一个 static List 被多个 service 方法共享修改,却只在某处加了迭代保护。

















