arraylist非线程安全,多线程或单线程遍历时直接修改会因modcount与expectedmodcount不匹配触发concurrentmodificationexception,这是fail-fast机制主动抛出的异常,旨在及时暴露数据不一致风险。

ArrayList 本身不是线程安全的,当多个线程同时读写它,尤其是“一边遍历、一边修改”时,ConcurrentModificationException 就很容易出现。这不是偶然报错,而是它内部的fail-fast(快速失败)机制主动抛出的异常,目的是防止数据状态不一致导致更隐蔽的问题。
核心原因:modCount 和 expectedModCount 不匹配
ArrayList 继承自 AbstractList,内部维护一个 modCount 变量,记录集合结构被修改的次数(比如 add、remove 操作都会使 modCount++)。而它的迭代器(Iterator)在创建时,会把当时的 modCount 值拷贝一份,存为 expectedModCount。
每次调用 iterator.next() 或 iterator.hasNext() 时,都会执行 checkForComodification() 方法,比较这两个值:
- 如果相等 → 正常继续遍历
- 如果不相等 → 立即抛出
ConcurrentModificationException
多线程场景下的典型触发过程
假设有两个线程 T1(遍历线程)和 T2(修改线程),共享同一个 ArrayList:
- T1 调用
list.iterator()→ 创建 Itr 对象,此时expectedModCount = modCount = 0 - T1 执行
iterator.next()→ 成功,未触发检查 - T2 执行
list.add("X")→modCount变为 1,但 T1 的expectedModCount仍是 0 - T1 再次调用
next()→checkForComodification()发现modCount(1) != expectedModCount(0)→ 抛异常
不只是“多线程”,单线程也会触发
即使只有主线程,只要在 for-each 或 Iterator 遍历过程中,直接调用 list.remove() 或 list.add(),同样会改变 modCount 而不更新 expectedModCount,下一次 next() 就会失败。
例如:
for (String s : list) { if ("A".equals(s)) list.remove(s); }这行 list.remove(s) 就是“非法修改”,for-each 底层就是 Iterator,所以等价于在迭代中直接改集合。
为什么设计成这样?
不是为了“阻止修改”,而是为了及时暴露问题。如果不检查,可能造成:
- 遍历时跳过元素(因为数组缩容/移动后索引错位)
- 数组越界(
ArrayIndexOutOfBoundsException) - 重复处理同一元素
- size 返回错误值(如本该 52 却显示 51)
fail-fast 是一种防御性设计,宁可提前失败,也不让程序带着脏数据继续跑。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











