应选用CopyOnWriteArrayList或ConcurrentHashMap等并发集合,而非捕获ConcurrentModificationException;前者适用于读多写少场景,后者适用于高并发键值操作,避免使用Collections.synchronizedMap或无效副本遍历。

多线程环境下遍历集合时修改结构,会触发 ConcurrentModificationException,这不是线程安全问题的“症状”,而是单线程 fail-fast 机制在多线程中暴露的必然结果。真正要做的不是捕获异常,而是选用语义匹配、行为可控的并发集合或同步策略。
优先用 CopyOnWriteArrayList(读多写少场景)
它不依赖 modCount 校验,迭代器基于创建时刻的数组快照,因此遍历时其他线程增删元素完全不会抛 CME,也无需加锁。
- 适用典型场景:监听器列表、配置缓存、广播通知等——读操作远多于写,且能接受遍历中看不到最新变更
- 注意写开销:每次 add/remove 都复制整个数组,不适合高频写或大数据量
- 示例:事件总线中遍历监听器时,监听器可安全调用 remove 自身,不影响当前循环
改用 ConcurrentHashMap(支持高并发读写)
它的 keySet()、values()、entrySet() 返回的迭代器是弱一致性的:不抛 CME,也不保证看到所有最新修改,但能反映某一时刻的稳定快照。
- 不能在迭代器中调用 remove() 或 put() 修改底层 Map;应使用 computeIfPresent、merge、remove(key, value) 等原子方法
- 适合需要并发读写、且对强实时性要求不苛刻的键值场景(如缓存、计数器)
- 避免用 Collections.synchronizedMap():仅方法级同步,遍历仍需手动加锁,易出错且性能差
必要时加显式同步,但要完整包裹遍历+修改逻辑
若必须用 ArrayList/HashMap 等非线程安全集合,且需强一致性(比如遍历中根据条件决定是否删除并立即生效),就用 synchronized 块严格保护整个操作流程。
立即学习“Java免费学习笔记(深入)”;
- 锁对象必须是集合本身或统一锁实例,不能只锁部分操作
- 例如:
synchronized(list) { for (String s : list) { if (needRemove(s)) list.remove(s); } } - 此时 Iterator 的 modCount 校验依然有效,但因所有修改都在同一临界区内,不会触发异常
避免无效“快照”绕过方案
有人把集合转成新 ArrayList 再遍历,看似不报错,实则引入数据陈旧、内存浪费、逻辑脱节等问题。
- 副本生成后,原集合的后续变更对本次遍历不可见
- 若遍历逻辑依赖实时状态(如跳过刚被标记为“待删”的项),副本无法支撑
- 这不是解决并发问题,只是掩盖检测机制——该用并发集合的地方,别靠复制硬扛


















