concurrentmodificationexception 是因迭代器检测到集合被结构性修改而触发的异常;它通过比对 modcount 和 expectedmodcount 实现,仅 iterator.remove() 能安全同步状态,其他 add/remove/clear 等操作均会引发该异常。

ConcurrentModificationException 是怎么被触发的
当你在用 Iterator 遍历集合时,调用 c.remove(obj) 或 c.add(obj),几乎一定会抛出 ConcurrentModificationException。这不是“运气不好”,而是 Java 故意设计的保护机制——它通过维护一个叫 modCount(修改计数)的字段,和迭代器内部的 expectedModCount 做比对,一旦不一致就立刻报错。
-
modCount在每次调用add/remove/clear等结构性方法时自增 -
Iterator构造时会把当前modCount复制给自己的expectedModCount - 每次调用
next()或hasNext()前,都会执行checkForComodification()检查两者是否相等 - 所以哪怕你只删了一个元素,后续再调一次
next()就崩
为什么 Iterator.remove() 是唯一安全的删除方式
Iterator.remove() 不是“绕过检查”,而是主动同步状态:它在调用集合底层 remove() 的同时,把 expectedModCount 重新设为当前 modCount,让下一次检查能通过。
- 它只能删掉上一次
next()返回的那个元素(即lastRet位置),不能随意删任意值 - 必须在
next()之后、且未被删过的情况下调用,否则抛IllegalStateException - 不能在
for-each循环里用——因为语法糖隐藏了Iterator,你根本拿不到引用
Iterator<String> it = c.iterator();
while (it.hasNext()) {
String str = it.next();
if ("#".equals(str)) {
it.remove(); // ✅ 安全
// c.remove(str); // ❌ 立刻触发 CME
}
}
哪些操作算“结构性修改”
不是所有修改都会触发异常。“结构性修改”特指改变集合大小或物理存储结构的操作,和“改内容”有本质区别:
- 会触发 CME:
add()、remove()、clear()、retainAll()、removeAll() - 不会触发 CME:
set()(仅替换某索引处元素)、get()、contains()、size() - 注意:
ArrayList.set(i, x)安全;但LinkedList.set(i, x)内部可能涉及节点调整,某些 JDK 版本仍会更新modCount,建议统一按“可能触发”处理
想边遍历边增删?别硬刚,换思路
硬要在迭代中增删,本质上是在对抗迭代器的设计契约。与其反复踩坑,不如选更匹配的方案:
- 要过滤元素(如删掉所有
"#")→ 收集待删项,遍历完再批量removeAll() - 要构建新集合 → 用
Stream.filter()或新建ArrayList+add() - 高并发读多写少 → 考虑
CopyOnWriteArrayList,但注意它的迭代器看到的是快照,删完也看不到效果 - 真需要边走边改且单线程 → 用传统
for (int i = 0; i ,删元素时手动 <code>i--补位
最常被忽略的一点:即使没报异常,用集合方法删元素也可能导致漏删或重复遍历——因为底层数组位移后,迭代器游标还按原节奏走。这比崩溃更危险,它悄悄错得离谱。










