处理ConcurrentModificationException的核心是选择线程安全集合或正确同步访问逻辑,而非线程隔离;优先使用CopyOnWriteArrayList、ConcurrentHashMap等并发集合,其次通过synchronized保证迭代与修改互斥,或避免迭代中修改原集合。

处理 ConcurrentModificationException 的核心不是“线程隔离”,而是避免在迭代过程中被其他线程(或同一线程)结构性修改集合。所谓“线程隔离”容易误解为把数据拷贝到每个线程独占——这虽可行,但不是最常用或最高效的解法;真正关键的是选择**线程安全的集合类型**或**正确同步访问逻辑**。
用线程安全的替代集合
Java 提供了多种专为并发设计的集合,它们内部已处理好迭代与修改的协调,无需额外加锁:
-
CopyOnWriteArrayList:适合读多写少场景。每次写操作复制整个数组,迭代器基于快照,因此不会抛出ConcurrentModificationException; -
ConcurrentHashMap:支持安全的遍历(如forEach、entrySet().iterator()),其迭代器是弱一致性的,不抛 CME; -
ConcurrentLinkedQueue/BlockingQueue实现类:适用于队列类操作,提供无锁或阻塞式线程安全行为。
加锁保证迭代与修改互斥
若必须使用 ArrayList、HashMap 等非线程安全集合,且需在多线程环境下边遍历边可能被修改,则需显式同步:
- 用
synchronized块包裹**整个迭代过程 + 所有可能修改该集合的代码段**,确保同一时刻只有一个线程能读或写; - 锁对象应统一(如用集合本身或专用锁对象),避免不同锁导致的竞态;
- 注意:仅同步迭代代码而不同步修改代码,或反之,仍会触发 CME。
避免在迭代中修改原集合(推荐优先级最高)
很多 CME 其实源于单线程误操作(比如 for-each 循环里调 list.remove())。即使在多线程环境,也应先收集待操作元素,再统一处理:
立即学习“Java免费学习笔记(深入)”;
- 用
Iterator.remove()是唯一安全的边遍历边删除方式(仅限单线程场景); - 多线程下更稳妥做法是:先用
stream().filter().collect()或removeIf()(内部已同步处理)生成新集合,或用CopyOnWriteArrayList的removeIf; - 若需根据条件更新,可先收集 key/id 列表,再由专门线程或同步块批量更新。
不要依赖“线程本地副本”解决本质问题
用 ThreadLocal 为每个线程维护集合副本,看似隔离,但带来新问题:
- 副本间数据不同步,无法反映真实状态;
- 内存开销大,尤其集合较大或线程数多时;
- 不能解决“多个线程共同维护一份数据”的业务需求。
它适用于纯计算上下文(如缓存解析结果),而非共享数据的并发修改场景。
本质上,CME 是一个设计信号:当前集合的使用方式与它的并发契约冲突。选对集合、明确读写边界、分离迭代与修改动作,比强行做线程隔离更直接有效。


















