concurrentmodificationexception在单线程下也会触发,本质是迭代器通过modcount与expectedmodcount校验发现集合被非迭代器方式结构性修改;正确做法是使用iterator.remove()、removeif()或先收集后批量删除。

ConcurrentModificationException 在单线程下完全可能触发,它和“多线程”没有必然关系。本质是迭代器检测到集合结构被“非预期方式”修改了——只要你在用 for-each 或 Iterator 遍历时,直接调用 list.remove()、map.remove() 这类集合自身的方法删元素,就稳稳报错。
为什么单线程也会崩
ArrayList、HashMap 等集合内部有个叫 modCount 的计数器,每次 add、remove、clear 等结构性操作都会让它 +1。而 Iterator 创建时,会把当时的 modCount 快照为 expectedModCount。之后每次 next() 或 hasNext() 前,都会检查两者是否一致:
- 一致 → 继续遍历
- 不一致 → 立刻抛 ConcurrentModificationException
你在 for-each 里写 list.remove(s),modCount 就变了,但迭代器里的 expectedModCount 还是老值,下一次 next() 一校验就失败。
典型错误写法(单线程也挂)
这些代码哪怕只在一个线程里跑,也必报异常:
for (String s : list) { if (s.equals("x")) list.remove(s); }Iterator it = list.iterator(); while(it.hasNext()) { it.next(); list.remove("x"); }-
list.stream().forEach(s -> { if (s.isEmpty()) list.remove(s); });(stream 底层仍是 Iterator)
单线程安全删除的正确姿势
关键原则:让“删动作”和“迭代状态”同步更新,别绕开迭代器。
-
用 Iterator.remove():这是唯一被设计用来边遍历边删的方式
例:Iterator<string> it = list.iterator(); while(it.hasNext()) { String s = it.next(); if (s.length() > 10) it.remove(); }</string> -
用 removeIf()(JDK 8+):集合自己控制过程,不走外部 Iterator 校验
例:list.removeIf(s -> s == null || s.trim().isEmpty()); -
先收集再批量删:遍历时只记要删的元素,结束后调 removeAll()
例:List<string> toRemove = new ArrayList(); for (String s : list) if (s.startsWith("tmp")) toRemove.add(s); list.removeAll(toRemove);</string> - 用 CopyOnWriteArrayList(读多写少):写操作复制新数组,遍历时不受影响,但注意内存开销和弱一致性
容易忽略的关键点
这个异常不是在“写的时候”抛的,而是在“读的下一次 next()”时才暴露。也就是说,出错位置的代码行未必是问题根源——真正的问题是前面那句 list.remove()。调试时别只盯着报错行,要回溯整个遍历逻辑中有没有混用集合原生修改方法。










