modCount是ArrayList等集合的结构性修改计数器,用于迭代器fail-fast机制;迭代器创建时复制该值,每次操作前校验,不一致则抛ConcurrentModificationException。

modCount 是什么,为什么它能触发 ConcurrentModificationException
modCount 是 ArrayList(以及所有继承自 AbstractList 的集合)内部维护的一个修改计数器,每次调用 add、remove、clear 等结构性修改方法时自增。它的存在不是为了业务逻辑,纯粹服务于迭代器的「快速失败」(fail-fast)机制。
迭代器在创建时会把当前的 modCount 值拷贝到自己的 expectedModCount 字段中;每次调用 next() 或 hasNext() 前,都会检查两者是否相等。一旦不等,立刻抛出 ConcurrentModificationException —— 这个异常名字有误导性:它不只在多线程下触发,单线程里边遍历边修改也会炸。
常见错误现象:
- 循环中用
list.remove(obj)删除元素 → 抛ConcurrentModificationException - 用增强 for 循环(本质是迭代器)遍历时调用
list.add()→ 同样炸 - 多个迭代器共享同一个
ArrayList实例,一个改了,另一个 next 就失败
怎么手动验证 modCount 的变化行为
不能直接访问 modCount(它是 package-private),但可以通过反射或调试器观察;更实用的方式是借助迭代器的异常时机反推。
实操建议:
- 写一个最小复现:新建
ArrayList,add 两个元素,用iterator()获取迭代器,然后在第一次next()后立即调用list.remove(0),再调it.next()→ 必然抛异常 - 注意:
list.remove(Object)和list.remove(int)都会改modCount;但list.get(i)、list.set(i, x)不会,它们不算结构性修改 - 如果想“绕过”检测,必须用迭代器自己的
remove()方法(它会同步更新expectedModCount),而不是 list 的 remove
为什么 CopyOnWriteArrayList 没有 modCount 问题
CopyOnWriteArrayList 的迭代器是弱一致性的,底层持有一份创建时刻的数组快照,所有修改都发生在新副本上,原快照不受影响。因此它根本不依赖 modCount 做校验,自然不会抛 ConcurrentModificationException。
但这不是银弹:
- 读操作无锁但可能读到旧数据(最终一致性)
- 写操作要复制整个数组,内存和 CPU 开销大,只适合读多写极少场景
- 它的迭代器
remove()是空操作(不支持),调用即抛UnsupportedOperationException
所以别因为怕 modCount 就盲目换容器 —— 先确认你真需要并发安全,还是只是写法错了。
实际开发中怎么避开 modCount 踩坑
绝大多数 ConcurrentModificationException 都是单线程逻辑错误,跟并发无关。关键是要分清「谁在改结构」。
推荐做法:
- 遍历时删元素,一律用
Iterator.remove(),例如:for (Iterator<String> it = list.iterator(); it.hasNext();) { String s = it.next(); if (s.isEmpty()) it.remove(); // 安全 } - 需要批量删除?先收集待删索引或对象,遍历完再用
removeAll()或倒序remove(int) - 增强 for 循环里绝对不要出现任何
list.xxx()结构修改调用 - 调试时留意堆栈里异常是否出现在
hasNext()或next()内部 —— 这是典型的 modCount 失配信号
modCount 本身不可见、不可控,它的价值在于暴露设计契约:迭代器与容器状态必须严格绑定。一旦打破这个契约,JVM 就用异常把你拽回来 —— 这不是缺陷,是保护。

















