concurrentmodificationexception是fail-fast机制的主动保护,非bug;根本原因是迭代器创建时快照modcount,每次next()校验是否被修改,多线程共享迭代器或并发修改集合均触发异常。

Java 迭代器本身不支持多线程并发迭代,一旦多个线程共享同一个 Iterator 实例(比如都调用它的 next()),或一边迭代一边被其他线程修改集合,就会触发 ConcurrentModificationException——这不是 bug,而是设计上的“快速失败”(fail-fast)保护机制。
为什么直接共享迭代器会出错
每个 Java 集合的迭代器(如 ArrayList.Itr)在创建时会记录当前集合的修改计数 modCount,存为 expectedModCount。每次调用 next() 或 hasNext() 前都会检查二者是否一致。只要集合被任何线程增删元素(哪怕只是另一个线程改了),modCount 就变,检查失败即抛异常。
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
注意:这和“线程安全集合”不是一回事。即使你用了 Collections.synchronizedList() 或 Vector,它们只保证单个方法调用原子,但无法保证迭代过程的逻辑一致性——因为迭代是跨多次方法调用的状态行为,而迭代器对象本身仍是非线程安全的。
真正可行的多线程迭代方案
-
为每个线程分配独立迭代器:不要共享一个
Iterator。把原始数据源(如List)传给每个线程,各自调用list.iterator()创建专属实例。这是最简单、最常用的方式,适用于只读遍历场景。 -
用线程安全的快照型集合:选用
CopyOnWriteArrayList。它的迭代器基于创建时刻的数组快照,无论其他线程如何增删,迭代器始终看到一致的旧视图,不会抛异常,也无需额外同步。 -
用并发集合 + 显式分片:对大集合预分块(如按索引区间切分为 N 段),每个线程处理一段子列表(
list.subList(i, j)),再各自迭代。注意subList返回的是原集合视图,若原集合被并发修改仍可能出问题,所以更稳妥的做法是先复制成新ArrayList再交给线程。 -
避免迭代器,改用流式并行处理:Java 8+ 可用
list.parallelStream().forEach(...)。底层由ForkJoinPool自动分片调度,不依赖传统迭代器,天然规避游标冲突。适合计算密集型任务,但要注意副作用(如修改共享变量)需加锁或用原子类。
哪些做法看似合理实则危险
- 用
synchronized包裹整个while (it.hasNext()) { it.next(); ... }循环:虽然能阻止并发修改异常,但把迭代变成串行,完全失去并发意义,且容易引发死锁(尤其嵌套锁)。 - 在 for-each 循环中调用
list.remove():for-each 底层就是迭代器,直接调集合 remove 会立刻触发ConcurrentModificationException;必须用iterator.remove(),且仅限单线程内安全删除。 - 认为
ConcurrentHashMap的迭代器可多线程共用:它的迭代器是弱一致性的(fail-safe),允许并发修改,但不保证任意时刻看到全部最新变更,且多个线程同时调用同一个Iterator的next()依然会互相干扰游标位置——它解决的是“读写不互斥”,不是“多读共享游标”。
核心原则就一条:迭代器是状态持有者,不是无状态工具。多线程下要安全,要么隔离状态(每线程一副本),要么放弃状态(用快照或分片),要么绕过状态(用并行流)。没有“给迭代器加个锁就能并发”的捷径。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










