concurrentmodificationexception是fail-fast机制的主动保护,源于modcount与expectedmodcount不一致;需理解设计意图、触发条件、规避方案及底层原理。

Java 集合迭代失效(ConcurrentModificationException)不是“bug”,而是 fail-fast 机制的主动保护。大厂面试不考死记异常名称,而是看你是否理解设计意图、触发条件、规避逻辑和底层原理——答对这四层,才算真正通关。
一、为什么一改就抛 ConcurrentModificationException?
核心在于modCount(修改计数器)与 expectedModCount(期望计数器)不一致。几乎所有集合(ArrayList、HashMap、HashSet 等)的内部迭代器在创建时会记录当前 modCount 值为 expectedModCount;每次调用 next() 或 remove() 前,都会校验二者是否相等。一旦集合被外部线程或同一线程的其他代码(比如 for-each 循环里直接 list.remove())修改,modCount 自增,校验失败即抛异常。
注意:该机制仅针对单线程下的“结构性修改”(如 add/remove 元素),不检测元素内容变更(如 set(i, x) 或对象字段赋值)。
二、常见“踩坑”写法及安全替代方案
以下写法在面试中高频出现,需能一眼识别并给出正确解法:
-
错误:for-each 中直接 remove
for (String s : list) { if (s.equals("a")) list.remove(s); } → 抛异常
✅ 正确:用 Iterator 的 remove()
Iteratorit = list.iterator();
while (it.hasNext()) {
if ("a".equals(it.next())) it.remove();
} -
错误:普通 for 循环正向遍历 + remove
for (int i = 0; i → 跳过下一个元素(索引错位)
✅ 正确:倒序遍历 或 使用 ListIterator -
错误:多线程共享非线程安全集合
ArrayList/HashMap 被多个线程同时读写 → 可能抛 CME,也可能数据错乱
✅ 正确:
• 读多写少 → Collections.synchronizedList(list)
• 写频繁 → CopyOnWriteArrayList(注意内存与实时性权衡)
• 并发复杂操作 → 使用 ConcurrentHashMap 替代 HashMap
三、底层源码级关键点(面试加分项)
能讲出以下任意两点,技术深度立刻拉开差距:
- ArrayList$Itr 的 checkForComodification() 方法是校验入口,它在 next()、remove()、hasNext() 中都被调用
- modCount 是 AbstractList 的 protected 字段,子类(如 ArrayList)在 add、remove、clear 等方法中显式递增
- fail-fast 不是线程安全保证,只是快速失败提示;它无法防止并发修改,也不能替代同步机制
- CopyOnWriteArrayList 迭代器不检查 modCount,因为其迭代器基于快照(snapshot),写操作不影响正在遍历的数组
四、如何向面试官展现系统性思维?
别只答“怎么修”,要带上下文判断:
- 先问场景:是单线程遍历时误删?还是多线程竞争?数据量级多大?实时性要求高吗?
- 再选方案:单线程 → Iterator.remove();高并发读 → CopyOnWriteArrayList;需强一致性写 → 加锁或使用并发集合
- 最后提一句边界:CopyOnWriteArrayList 写操作成本高、迭代器看不到最新写入;ConcurrentHashMap 的 keySet() 迭代仍可能不反映实时更新(弱一致性)
不复杂但容易忽略。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











