concurrentmodificationexception 是检测机制而非安全保证,依赖未同步的 modcount 与 expectedmodcount 比对,多线程下可能漏检,单线程中迭代期间直接修改集合也会触发;iterator.remove() 同步更新两者,list.remove() 不通知迭代器致下次 next() 失败;for-each 等价于显式迭代器,普通 for 循环无此限制。

ConcurrentModificationException 不是并发安全开关
它不保证一定能捕获所有并发修改,也不代表没抛异常就线程安全。这个异常只是「碰巧被检测到」的结果——底层靠 modCount 和 expectedModCount 两个整数比对,而这两个值没有同步保护,也没有 volatile 语义。所以多线程下可能因指令重排、缓存不一致等原因漏检;单线程里只要在 iterator.next() 前后改了集合结构,照样报错。
为什么 ArrayList.iterator().remove() 安全,但 list.remove() 就不行
关键在修改计数器的协同更新:
-
iterator.remove()内部会同步更新自己的expectedModCount,并调用集合的remove()方法(该方法会自增modCount),使两者保持一致 -
list.remove()只改modCount,完全不管迭代器里的expectedModCount,下次next()一检查就失败 - 哪怕只删一个元素,用错方式也会触发异常;不是“删多了才错”,而是“谁动的结构、谁负责通知迭代器”
fail-fast 在 for-each 和普通 for 循环里的表现差异
for-each 底层就是迭代器,所以和显式用 Iterator 完全等价,同样受 ConcurrentModificationException 约束;而普通 for (int i = 0; i 绕过了迭代器机制,不会触发 fail-fast,但会引发逻辑错误:
- 删除元素后索引错位,跳过下一个元素(常见于未做
i--补偿) - 遍历时
list.size()动态变化,可能导致数组越界或遍历不全 - 它“不报错”,不代表“做对了”——这是更隐蔽的 bug 来源
别把 CopyOnWriteArrayList 当万能解药
它确实不抛 ConcurrentModificationException,因为读操作永远基于快照,写操作新建副本。但代价明显:
- 写操作开销大:每次
add()或remove()都要复制整个数组 - 读取到的不是实时数据:迭代期间其他线程的修改对当前迭代器不可见
- 仅适合读多写少场景(如监听器列表),不适合高频更新的业务集合
- 它实现的是 fail-safe,不是 fail-fast——机制完全不同,不能混为一谈










