在Java集合框架的使用过程中,ConcurrentModificationException是一个十分常见的运行时异常,它通常在我们对集合进行迭代操作的同时修改集合结构时抛出,很多开发者在遇到这个异常时往往不知道具体的触发逻辑,也不清楚背后的设计原因。

什么是ConcurrentModificationException
ConcurrentModificationException是Java标准库中的一个运行时异常,继承自RuntimeException,它的官方定义是当检测到对象的并发修改,但不允许这种修改时抛出的异常。不过需要特别说明的是,这个异常不仅会在多线程并发修改集合时抛出,单线程场景下如果违反了集合的迭代规则也会触发。
单线程场景下的异常产生原因
我们以最常用的ArrayList集合为例,来分析单线程下该异常的产生逻辑。ArrayList内部维护了一个modCount变量,这个变量记录了集合结构被修改的次数,每次对集合进行添加、删除元素等会改变集合大小的操作时,modCount都会自增1。
当我们通过iterator()方法获取ArrayList的迭代器时,迭代器内部会初始化一个expectedModCount变量,初始值等于当前集合的modCount。在迭代过程中,每次调用next()方法获取下一个元素之前,迭代器都会先检查modCount和expectedModCount是否相等,如果不相等就会抛出ConcurrentModificationException。
下面是一段会触发该异常的示例代码:
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class ConcurrentModificationDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
// 获取迭代器
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String item = iterator.next();
if ("b".equals(item)) {
// 直接调用集合的remove方法修改集合结构
list.remove(item);
}
}
System.out.println(list);
}
}
运行上面的代码就会抛出ConcurrentModificationException,原因是调用list.remove(item)时,集合的modCount会自增1,但是迭代器内部的expectedModCount没有同步更新,下一次调用iterator.next()时检查到两个值不相等,就会抛出异常。
多线程场景下的异常产生原因
多线程场景下,如果多个线程同时操作同一个集合,其中一个线程在迭代集合,另一个线程修改集合结构,同样会导致modCount和expectedModCount不一致,从而抛出ConcurrentModificationException。因为ArrayList本身不是线程安全的集合,没有对并发修改做任何同步处理,所以多线程操作下更容易出现这类问题。
下面的示例模拟了多线程触发该异常的场景:
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class MultiThreadConcurrentDemo {
private static final List<String> list = new ArrayList<>();
static {
for (int i = 0; i < 10; i++) {
list.add("item" + i);
}
}
public static void main(String[] args) {
// 线程1负责迭代集合
Thread thread1 = new Thread(() -> {
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
try {
// 休眠10毫秒,让线程2有机会修改集合
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
// 线程2负责修改集合
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
list.add("newItem" + i);
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread1.start();
thread2.start();
}
}
如何避免ConcurrentModificationException
针对不同的场景,我们可以采用不同的方式避免该异常:
- 单线程迭代修改场景:使用迭代器自带的
remove()方法删除元素,这个方法会在删除元素后同步更新expectedModCount的值,保证和modCount一致。示例代码如下:
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class SafeRemoveDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String item = iterator.next();
if ("b".equals(item)) {
// 使用迭代器的remove方法,不会触发异常
iterator.remove();
}
}
System.out.println(list);
}
}
- 多线程场景:使用线程安全的集合类,比如
CopyOnWriteArrayList,这类集合的迭代器是基于集合的快照实现的,迭代过程中不会检测modCount,所以不会抛出ConcurrentModificationException。或者使用Collections.synchronizedList包装集合,在迭代时手动加锁保证同步。 - 如果需要批量修改集合,可以先记录需要修改的元素,迭代完成后再统一进行修改,避免在迭代过程中直接操作原集合。
总结
ConcurrentModificationException的核心触发逻辑是集合的modCount和迭代器的expectedModCount不一致,无论是单线程违反迭代规则修改集合,还是多线程并发修改集合,本质都是导致了这两个变量的值不匹配。在实际开发中,我们需要根据具体的使用场景选择合适的集合类和操作方式,避免触发这类异常,保证程序的稳定运行。
ConcurrentModificationExceptionJava集合迭代器modCountexpectedModCount修改时间:2026-07-05 02:03:26