关于ConcurrentModificationException 异常介绍
在一个线程遍历集合的时候(如ArrayList,HashMap),结构被修改(如remove, add),就会抛出这个异常。
是一个fail fast机制,为了在并发修改的时候发现问题,而不是返回错误数据。
出现的原因
源于ArrayList中的modCount字段
protected transient int modCount = 0;
这个字段的作用是记录结构的修改次数
还有Iterator中的expectedModCount字段,如果expectedModCount不等于modCount,就会抛出CME
private class Itr implements Iterator<E> {int cursor; // index of next element to returnint lastRet = -1; // index of last element returned; -1 if no suchint expectedModCount = modCount;public void remove() {try {ArrayList.this.remove(lastRet);cursor = lastRet;lastRet = -1;expectedModCount = modCount;} catch (IndexOutOfBoundsException ex) {throw new ConcurrentModificationException();}}
}
- modCount:记录集合被结构修改的次数(add、remove、clear等)
- expectedModCount:迭代器期望的修改次数
在代码中出现CME的情况
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");for (String s : list) {list.remove(s); // 抛 ConcurrentModificationException
}
以上代码的链表就会发生结构变化,究其根本就是ArrayList修改了但是没有同步到Iterator迭代器,导致modCount != expectedCount从而抛出CME
正确的写法
1)使用Iterator.remove()方法
Iterator<String> it = list.iterator();
while (it.hasNext()) {String s = it.next();if (s.equals("A")) {it.remove(); // ✔ 不会抛 CME}
}
原因是:
public void remove() {// ...code...try {ArrayList.this.remove(lastRet); // 调用集合的removecursor = lastRet;lastRet = -1;expectedModCount = modCount; // 关键:同步更新!} catch (IndexOutOfBoundsException ex) {throw new ConcurrentModificationException();}
}
2)使用CopyOnWriteArrayList
CopyOnWriteArrayList适合读多写少
List<String> list = new CopyOnWriteArrayList<>();
list.add("A");
list.add("B");for (String s : list) {list.remove(s); // ✔ 完全没问题
}
因为CopyOnWriteArrayList修改时会创建新的数组,读数据还是遍历旧数组,不会发生CME
3)使用 for 循环倒序遍历
for (int i = list.size() - 1; i >= 0; i--) {list.remove(i); // ✔ 不会 CME
}
4)使用removeIf()
list.removeIf(s -> s.equals("A"));
多线程下出现CME的情况
List<Integer> list = new ArrayList<>();new Thread(() -> {list.add(1);
}).start();new Thread(() -> {for (Integer i : list) {System.out.println(i); // ❌ 可能 CME}
}).start();
解决方案:
使用并发集合:
- ConcurrentHashMap
- CopyOnWriteArrayList
- ConcurrentLinkedQueue
- ConcurrentSkipListMap
Fail-fast是什么
指的是程序在运行代码的过程中,如果遇到错误或者异常状态,立即抛出异常停止运行,避免在后续的操作中引发更严重的数据不一致