concurrentmodificationexception 在单线程中遍历集合时调用 add()、remove() 等结构性修改方法即触发;因迭代器校验 modcount 与 expectedmodcount 不等而抛出。

ConcurrentModificationException 是怎么被触发的
当你用 Iterator 遍历 ArrayList 或 HashMap 时,如果在遍历中途调用了 add()、remove()、clear() 等修改集合结构的方法,几乎一定会抛出 ConcurrentModificationException——这不是并发专属错误,单线程里也照抛不误。
-
modCount是集合内部一个 volatile 计数器,每次结构性修改(增删改 size)都会自增 - 迭代器初始化时会把当时的
modCount值存进自己的expectedModCount - 每次调用
next()或hasNext()前,都会校验modCount == expectedModCount;不等就立刻 throw - 注意:
for (int i = 0; i 这种普通 for 循环不会触发该检查,所以不会抛这个异常
为什么 modCount 不检测“值修改”而只管“结构修改”
modCount 的设计目标从来不是监控内容变更,而是保护迭代器逻辑的正确性。比如 ArrayList 的 set(int index, E element) 方法就不会增加 modCount,因为它不改变元素个数、不移动数组位置、不影响迭代顺序。
- 只有影响
size、触发扩容、导致数组索引偏移的操作才算“结构性修改” -
HashMap.put()在新增 key 或扩容时会增modCount,但覆盖已有 key 不会 - 这说明 fail-fast 不是“防所有修改”,而是“防破坏遍历契约的修改”
哪些集合有 fail-fast,哪些没有
关键看源码里有没有 modCount 字段和对应的校验逻辑。标准 JDK 集合中:
- 有 fail-fast:
ArrayList、LinkedList、HashMap、TreeMap、HashSet(底层是HashMap) - 无 fail-fast:
CopyOnWriteArrayList(写时复制,迭代器用快照)、ConcurrentHashMap(分段锁 + CAS,不依赖modCount) - 特别注意:
Vector和Stack虽然线程安全,但仍有modCount和 fail-fast 行为(因为继承自AbstractList)
怎么安全地边遍历边删元素
最常见需求:遍历列表,删掉满足条件的项。直接用 list.remove() 必崩;正确姿势只有两种:
- 用迭代器自己的
remove()方法:it.remove()—— 它内部会同步更新expectedModCount - 收集待删索引/对象,遍历完再批量删(如用
removeIf()):list.removeIf(x -> x.isExpired()) - 绝对别写:
for (String s : list) { if (s.isEmpty()) list.remove(s); }—— 这是经典坑
真正容易被忽略的是:即使你没显式启多线程,只要在 lambda、监听器、回调里偷偷改了正在被迭代的集合,一样会崩。fail-fast 不认“意图”,只认“动作”。









