ConcurrentModificationException
是 Java 集合框架中常见的运行时异常,主要在使用迭代器遍历集合时,检测到集合结构被修改而抛出
1. 产生原因
-
快速失败(Fail-Fast)机制:
当一个集合(如ArrayList
、HashMap
等)被迭代时,内部会维护一个modCount
字段记录结构修改次数(如添加、删除元素)。
如果迭代器检测到modCount
与初始值不一致,会立即抛出ConcurrentModificationException
,以避免潜在的数据不一致。 -
根本矛盾:
迭代器创建时假定集合结构不变,若在迭代期间其他线程或当前线程通过非迭代器方法修改了集合,就会触发异常。
2. 常见情形
情形 1:单线程中错误修改集合
List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));
for (String element : list) { // 增强for循环使用迭代器
if (element.equals("b")) {
list.remove(element); // 直接修改集合,modCount变化
}
}
// 抛出ConcurrentModificationException
情形 2:多线程并发修改
List<String> list = new ArrayList<>();
// 线程1:遍历集合
new Thread(() -> {
for (String element : list) {
System.out.println(element);
Thread.sleep(100);
}
}).start();
// 线程2:修改集合
new Thread(() -> {
Thread.sleep(200);
list.add("d"); // 破坏线程1迭代器的一致性
}).start();
情形 3:错误使用子集合
List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));
List<String> subList = list.subList(0, 2); // 子集合与原集合共享数据
subList.add("x"); // 修改子集合会影响原集合的modCount
for (String s : list) { // 遍历原集合时抛出异常
System.out.println(s);
}
情形 4:迭代器混用
List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));
Iterator<String> it1 = list.iterator();
Iterator<String> it2 = list.iterator();
it1.next();
it2.remove(); // 第二个迭代器修改集合,导致第一个迭代器失效
it1.next(); // 抛出异常
3. 注意事项与规范
正确删除元素的方式
-
使用迭代器的
remove()
方法:Iterator<String> it = list.iterator(); while (it.hasNext()) { String element = it.next(); if (element.equals("b")) { it.remove(); // 安全删除,更新迭代器内部状态 } }
-
Java 8+ 使用
removeIf()
:list.removeIf(element -> element.equals("b"));
多线程环境的安全措施
-
使用线程安全的集合:
List<String> list = Collections.synchronizedList(new ArrayList<>()); // 或直接使用 CopyOnWriteArrayList List<String> list = new CopyOnWriteArrayList<>();
CopyOnWriteArrayList
在修改时创建新数组,迭代器基于旧数组,不会抛出异常。
-
显式同步:
List<String> list = Collections.synchronizedList(new ArrayList<>()); synchronized (list) { // 同步块保护遍历 for (String element : list) { System.out.println(element); } }
避免子集合与原集合混用
-
对子集合的修改会影响原集合,反之亦然。若需独立操作,应复制数据:
List<String> subList = new ArrayList<>(list.subList(0, 2)); // 复制子集合
避免在循环中动态添加元素
- 若需动态添加,可使用临时集合收集修改,迭代结束后合并:
List<String> toAdd = new ArrayList<>(); for (String element : list) { if (condition) { toAdd.add("newElement"); } } list.addAll(toAdd); // 迭代结束后修改
4. 例外情况
-
线程安全集合:
ConcurrentHashMap
、CopyOnWriteArrayList
等采用不同的迭代机制,允许并发修改,不会抛出异常。但迭代器可能反映弱一致性(如看到部分修改)。 -
Spliterator:
Java 8 引入的分割迭代器,部分实现支持并发修改(如ArrayList
的Spliterator
)。
总结
场景 | 解决方案 |
---|---|
单线程删除元素 | 使用迭代器的 remove() 方法 |
多线程遍历与修改 | 使用 CopyOnWriteArrayList 或同步 |
动态添加元素 | 先收集到临时集合,迭代后合并 |
子集合操作 | 复制数据到新集合,避免共享 |
哈哈下面的代码也能正常运行
@Test
void test() {
List<String> a = new ArrayList<>();
a.add("1");
a.add("2");
a.add("3");
for (Iterator<String> iterator = a.iterator(); iterator.hasNext(); ) {
String next = iterator.next(); // cursor++
if ("1".equals(next)) {
a.remove(iterator.next()); // cursor++ && size--
a.remove(next); // size--
a.add(next); // size++
foo();
} // cursor == 2 && size == 2
}
}