concurrentmodificationexception报的是“迭代器检测到集合结构被意外修改”的错,而非多线程并发修改;单线程中for-each遍历时调用list.remove()也会触发,因expectedmodcount与modcount不匹配。

ConcurrentModificationException到底在报谁的错
它不报“多线程并发修改”的错,而是报“迭代器检测到集合结构被意外修改”的错。哪怕单线程里,一边用for-each遍历ArrayList,一边调用list.remove(),也会触发——因为Iterator内部维护了一个expectedModCount,和集合自身的modCount对不上就直接抛异常。
- 常见错误现象:
java.util.ConcurrentModificationException出现在iterator.next()或forEach循环体中调用remove()/add()时 - 不是所有集合都这样:
CopyOnWriteArrayList、ConcurrentHashMap这类线程安全容器不依赖modCount校验,不会抛这个异常 - 注意陷阱:用
stream().forEach()遍历时调用list.remove(),同样会触发——因为底层仍是Iterator
怎么安全地边遍历边删元素(Java 8+)
别碰iterator.remove()以外的任何删除方式,除非你明确知道当前集合支持哪种语义。
- 推荐方案:用
removeIf(),它由集合自己控制迭代和删除逻辑,绕过Iterator校验list.removeIf(x -> x == null || x.isEmpty()); - 替代方案:收集待删元素再批量删
List<String> toRemove = new ArrayList<>();<br>for (String s : list) { if (s.length() > 10) toRemove.add(s); }<br>list.removeAll(toRemove); - 反模式:在
for-each里写list.remove(x),或用普通for循环但倒序没写对(比如从i=0开始删,索引会乱)
Fail-Fast不是线程安全机制,只是调试辅助
它的存在意义是快速暴露错误,而不是保证并发正确性。别指望靠它来“防止”多线程问题——它既不加锁,也不同步,只是在next()时做一次整数比对。
- 性能影响极小:一次整数比较,无内存屏障、无CAS开销
- 兼容性无风险:所有JDK版本的
ArrayList、HashMap等都保持该行为,但ConcurrentHashMap的keySet().iterator()是弱一致的,不抛此异常 - 容易被忽略的点:自定义集合如果继承
AbstractList但忘了更新modCount,会导致误报或漏报;子类重写add()却没调super.add(),也会破坏校验
为什么ConcurrentHashMap不抛这个异常
因为它压根不用modCount那一套。它的Iterator基于分段快照(segment-level snapshot)或CAS遍历,允许遍历时有其他线程修改,只保证“最多看到某次修改前的状态”,不保证实时一致性,所以也无需校验“是否被改过”。
立即学习“Java免费学习笔记(深入)”;
- 使用场景:高并发读多写少,且能接受弱一致性结果(如缓存、配置映射)
- 参数差异:
ConcurrentHashMap构造时的concurrencyLevel(JDK 7)或initialCapacity(JDK 8+)影响分段/桶数量,但和Fail-Fast无关 - 注意边界:虽然不抛
ConcurrentModificationException,但若在遍历entrySet()时调用clear(),仍可能看到部分旧值——这不是Bug,是设计契约









