可能有些同学以为fail-fast只是并发场景下才会出现的问题,其实不然。它本质上就是modCount与expectedModCount不一致时,抛出的一个ConcurrentModificationException。单线程下,只要违规操作,照样会触发这个异常。核心就一句话:迭代器创建之后,集合被外部修改了。
触发的前提条件
说到底,触发这个异常需要同时满足三个条件——少了任何一个,都报不出来:
- 必须使用迭代器(
iterator())或增强for循环遍历ArrayList,因为增强for循环底层就是迭代器; - 遍历过程中,有代码对ArrayList执行了结构性修改——注意,这里说的结构性修改,是指
add()、remove()、clear()这类会改变size的操作; - 这个修改不是通过当前迭代器自身的
remove()或add()(ListIterator才有add)方法完成的。
内部计数器是怎么工作的
ArrayList继承自AbstractList,里面定义了一个关键字段——protected transient int modCount = 0。每次结构性修改,modCount都会加1。而当你创建一个迭代器(比如ArrayList.Itr)时,它会在构造瞬间把当前的modCount复制一份,存为自己的expectedModCount。
之后每次调用next(),都会先执行checkForComodification(),检查这两个值是否相等。一旦发现modCount != expectedModCount,一句废话没有,直接抛出ConcurrentModificationException。

常见触发场景举例
下面这些写法,随便挑一个,都会触发异常:
- 增强for循环里直接调用
list.remove(x)——注意,不是迭代器的remove; - while + iterator遍历过程中,用
list.add(y)插入新元素; - 多线程环境下,线程A用iterator遍历,线程B同时调用
list.remove(); - 单线程也有坑:先获取iterator,再调用
list.clear(),之后再调用next(),照样翻车。
很多新手会想当然地以为“单线程就没问题”,结果踩了坑才反应过来。
为什么for-i循环有时不报错?
普通for循环——就是for(int i=0; imodCount,所以不会触发fail-fast机制。但千万别以为这就安全了。它不会自动跳过已删除的位置,搞不好就会抛出IndexOutOfBoundsException,或者跳过元素导致数据错乱。这属于隐性风险,不是安全替代方案,只是换了一种翻车的方式而已。