Java的ConcurrentModificationException异常介绍和解决方案
关于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 return
int lastRet = -1; // index of last element returned; -1 if no such
int 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); // 调用集合的remove
cursor = 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是什么
指的是程序在运行代码的过程中,如果遇到错误或者异常状态,立即抛出异常停止运行,避免在后续的操作中引发更严重的数据不一致

浙公网安备 33010602011771号