在Java集合框架的使用中,ConcurrentModificationException是开发者经常遇到的运行时异常,尤其在多线程操作集合或者单线程迭代集合时修改集合结构的场景下很容易触发,同时这类场景往往还伴随着线程安全的隐患,需要开发者掌握对应的处理方案。

ConcurrentModificationException的产生原因
ConcurrentModificationException的本质是集合的快速失败(fail-fast)机制导致的。大多数Java集合(如ArrayList、HashMap等)内部都维护了一个modCount变量,用来记录集合结构被修改的次数。当通过迭代器遍历集合时,迭代器会初始化一个expectedModCount变量,值等于当前集合的modCount。
如果在迭代过程中,集合的结构被修改(比如添加、删除元素),modCount会自增,此时迭代器下次调用next()或者hasNext()方法时,会发现expectedModCount和modCount不一致,就会抛出ConcurrentModificationException。
单线程场景下的触发示例
在单线程中使用增强for循环遍历集合时直接修改集合结构,就会触发该异常:
import java.util.ArrayList;
import java.util.List;
public class SingleThreadConcurrentModification {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
// 增强for循环本质是迭代器遍历,遍历过程中删除元素会触发异常
for (String item : list) {
if ("b".equals(item)) {
list.remove(item); // 直接修改集合结构,modCount变化,迭代器校验失败
}
}
}
}
处理ConcurrentModificationException的常用技巧
1. 使用迭代器自身的删除方法
如果是单线程场景下的遍历删除操作,可以使用迭代器的remove()方法,该方法会同步更新expectedModCount的值,避免校验失败:
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class IteratorRemoveDemo {
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)) {
iterator.remove(); // 迭代器自身的删除方法,不会触发异常
}
}
System.out.println(list); // 输出[a, c]
}
}
2. 遍历前复制集合副本
如果需要遍历集合的同时做较多修改操作,可以先复制一份集合副本,遍历副本的同时修改原集合,这样不会触发迭代器的校验逻辑:
import java.util.ArrayList;
import java.util.List;
public class CopyListDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
// 复制副本进行遍历
List<String> copyList = new ArrayList<>(list);
for (String item : copyList) {
if ("b".equals(item)) {
list.remove(item); // 修改原集合,遍历的是副本,不会触发异常
}
}
System.out.println(list); // 输出[a, c]
}
}
3. 使用并发集合替代普通集合
如果是多线程场景下的集合操作,普通集合的快速失败机制无法避免异常,此时可以使用Java并发包提供的并发集合,比如CopyOnWriteArrayList、ConcurrentHashMap等,这些集合的设计避免了快速失败机制,同时天然支持线程安全。
集合操作的线程安全问题及解决方案
除了ConcurrentModificationException,多线程操作普通集合还会带来数据不一致、脏读等线程安全问题,需要根据场景选择合适的解决方案。
1. 使用同步包装类
Java集合框架提供了Collections工具类的同步包装方法,可以将普通集合包装成线程安全的集合,比如Collections.synchronizedList()、Collections.synchronizedMap()等,这些包装后的集合所有操作都会加对象级别的锁,保证同一时间只有一个线程能修改集合。
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class SynchronizedListDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
// 包装成线程安全的List
List<String> synchronizedList = Collections.synchronizedList(list);
// 多线程添加元素
Thread t1 = new Thread(() -> {
for (int i = 0; i < 10; i++) {
synchronizedList.add("t1-" + i);
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 10; i++) {
synchronizedList.add("t2-" + i);
}
});
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(synchronizedList.size()); // 输出20,不会出现数据丢失
}
}
需要注意的是,使用同步包装类遍历集合时,仍然需要手动加锁,否则遍历过程中其他线程修改集合还是可能触发异常:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class SynchronizedListIterateDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
List<String> synchronizedList = Collections.synchronizedList(list);
synchronizedList.add("a");
synchronizedList.add("b");
// 遍历时需要手动加锁
synchronized (synchronizedList) {
for (String item : synchronizedList) {
System.out.println(item);
}
}
}
}
2. 使用并发集合类
同步包装类的锁粒度较粗,性能相对较低,高并发场景下更推荐使用并发包下的并发集合,比如CopyOnWriteArrayList适合读多写少的场景,写操作时会复制整个数组,读操作不需要加锁;ConcurrentHashMap采用分段锁或者CAS+synchronized的方式,并发性能远优于同步包装的HashMap。
import java.util.concurrent.CopyOnWriteArrayList;
public class CopyOnWriteArrayListDemo {
public static void main(String[] args) {
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
list.add("a");
list.add("b");
// 多线程遍历和修改都不会触发ConcurrentModificationException
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
list.add("t1-" + i);
}
});
Thread t2 = new Thread(() -> {
for (String item : list) {
System.out.println(item);
}
});
t1.start();
t2.start();
}
}
3. 手动加锁控制并发
如果使用的是自定义集合或者需要更细粒度的锁控制,可以通过synchronized关键字或者ReentrantLock手动加锁,保证集合操作的原子性:
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.ReentrantLock;
public class ManualLockDemo {
private final List<String> list = new ArrayList<>();
private final ReentrantLock lock = new ReentrantLock();
public void addItem(String item) {
lock.lock();
try {
list.add(item);
} finally {
lock.unlock();
}
}
public String getItem(int index) {
lock.lock();
try {
return list.get(index);
} finally {
lock.unlock();
}
}
}
不同场景的方案选择建议
可以根据实际场景选择合适的处理方案:
- 单线程遍历删除场景:优先使用迭代器的
remove()方法,简单且高效。 - 单线程遍历修改较多场景:可以复制集合副本再操作,避免异常。
- 多线程低并发场景:可以使用同步包装类,实现简单。
- 多线程高并发场景:优先选择对应的并发集合类,性能更好。
- 需要自定义锁逻辑场景:手动使用
synchronized或者ReentrantLock控制并发。
只要理解集合的快速失败机制原理,结合场景选择合适的处理方案,就能有效避免ConcurrentModificationException,同时保障集合操作的线程安全。
ConcurrentModificationExceptionJava并发线程安全集合操作修改时间:2026-07-13 03:27:35