modcount是arraylist等集合的结构性修改计数器,定义在abstractlist中,仅在add、remove、clear等结构变更时自增;迭代器创建时将modcount赋值给expectedmodcount,每次next()前校验二者是否相等,不等则立即抛concurrentmodificationexception,实现快速失败机制。

ArrayList 的 fail-fast 不是线程安全方案,而是一套“快速暴露问题”的检测逻辑。真正掌握它,关键在于看清 modCount 怎么记、expectedModCount 怎么用、以及两者不等时为何必须立刻报错——不是为了拦住并发,而是拒绝在脏数据上继续执行。
modCount 是什么:一个只计“结构变动”的 volatile 计数器
modCount 定义在 AbstractList 中,类型是 transient int(注意:JDK 8 及以后实际加了 volatile 修饰,保障跨线程可见性)。它不记录读操作,也不记录 set() 这类非结构性修改;只在以下操作中 +1:
- add() / addAll()
- remove() / removeAll() / retainAll()
- clear() / trimToSize()
- iterator().remove()(由迭代器触发的结构性修改)
它不保证原子性,也不提供同步语义——volatile 仅确保一个线程对 modCount 的修改能被另一个线程“较快看到”,这对 fail-fast 的检测已足够。
expectedModCount 是怎么来的:迭代器的“快照信任凭证”
每次调用 list.iterator(),内部 Itr 对象就会执行:
int expectedModCount = modCount;这个赋值发生在迭代开始前的瞬间,相当于给当前集合状态拍了一张“可信快照”。之后所有 next()、remove() 操作都会先调用 checkForComodification():
- 若 modCount == expectedModCount → 认为结构未变,继续遍历
- 若 modCount != expectedModCount → 立即抛出 ConcurrentModificationException
这个比对不依赖锁、不等待、不重试,纯粹是“此刻值 vs 当初值”的一次轻量判断。
多线程下异常如何真实触发:两个线程的时序竞争
假设线程 A 正在遍历,线程 B 同时删除元素:
- A 调用 iterator() → expectedModCount = 0(此时 modCount 为 0)
- B 调用 list.remove() → modCount 变为 1
- A 调用 next() → checkForComodification() 发现 1 ≠ 0 → 抛异常
注意:异常不一定出现在 B 修改后的第一次 next()。由于 JVM 指令重排和内存可见性延迟,可能稍有滞后,但只要检测到不一致,就绝不容忍继续执行——这就是“快速失败”的本质:宁可中断,不冒风险。
为什么说它不保护线程安全:三类典型失效场景
fail-fast 机制完全无法防止以下真实并发问题:
- 两个线程同时调 remove() → 可能导致数组越界或元素丢失(modCount 虽递增两次,但异常只由遍历线程捕获,另一线程仍静默执行)
- 线程 A 遍历时,线程 B 执行 list.set(i, x) → modCount 不变,无异常,但数据已被篡改
- 多个迭代器共存 → 每个都持有自己的 expectedModCount,彼此不感知,任一修改都会让其他迭代器后续调用失败
它只是个哨兵,职责是喊出“结构已变”,而不是站岗守门。










