ArrayList的fail-fast机制是调试辅助机制而非并发控制手段,通过modCount与expectedModCount校验迭代时结构一致性,不保证线程安全。

ArrayList 的 fail-fast 机制不是为多线程安全设计的,它只是在检测到“预期修改次数”与“实际结构修改次数”不一致时,主动抛出 ConcurrentModificationException,用以快速暴露错误——本质是**调试辅助机制,而非并发控制手段**。
modCount 与 expectedModCount 的协同校验
ArrayList 内部维护一个 modCount(modifications count),每次调用 add、remove、clear 等改变结构的方法时,该值自增。而迭代器(如 iterator() 或 listIterator() 返回的对象)在创建时会将当前 modCount 快照保存为自己的 expectedModCount。
每次调用迭代器的 next()、remove() 前,都会检查:
if (modCount != expectedModCount)- 不等则立即抛出
ConcurrentModificationException
这个检查发生在迭代过程中,而非修改发生瞬间。所以它不阻止并发修改,只在“后续迭代动作”中发现不一致后报错。
立即学习“Java免费学习笔记(深入)”;
多线程下触发异常的典型场景
两个线程共享同一个 ArrayList 实例,且未加同步:
- 线程 A 调用
iterator(),拿到迭代器,此时expectedModCount == modCount == 3 - 线程 B 同时调用
add("x"),modCount变为4 - 线程 A 继续调用
next(),检查发现modCount(4) != expectedModCount(3),抛异常
注意:异常不一定在 B 修改后立刻抛出,而是等到 A 下一次迭代操作时才被发现。这也意味着,如果线程 A 迭代完毕前 B 没有修改,或 B 修改后 A 不再调用 next(),异常可能不会出现——这正是 fail-fast 的“不可靠性”,它不保证 100% 捕获所有并发修改。
为什么不是线程安全机制?
fail-fast 不提供任何同步、阻塞或原子性保障:
- 它不加锁,不等待,不重试
- 它不防止多个线程同时写入导致数据错乱(如数组越界、元素丢失、size 不一致等)
- 它甚至无法检测到某些“非结构修改”(如通过索引直接设值:
list.set(i, x)不改变modCount,就不会触发检查)
也就是说,即使没抛 ConcurrentModificationException,程序也可能已处于不一致状态——异常只是冰山一角。
真正需要线程安全时该怎么做?
若需并发访问 ArrayList,应显式选择线程安全替代方案:
- 使用
Collections.synchronizedList(new ArrayList()):包装成同步列表,但迭代仍需手动同步(否则仍可能抛异常) - 使用
CopyOnWriteArrayList:读操作无锁,写操作复制底层数组;迭代器基于快照,不会抛ConcurrentModificationException,适合读多写少场景 - 用显式锁(如
ReentrantLock)保护整个 list 访问逻辑 - 改用不可变集合(如
ImmutableListfrom Guava)+ 每次修改生成新实例
不要依赖 fail-fast 来“保护”并发逻辑——它只是告诉你“你已经做错了”,而不是帮你“做对”。


















