问题现象
在日志里看到这样的堆栈:
java.util.ConcurrentModificationException: null at java.util.ArrayList.forEach(ArrayList.java:1260) at com.xxx.queryAllMyScenarioCardDetailInfo(ScenarioCardMyCustomServiceImpl.java:97)
这其实是 Java 集合框架里的经典坑。ArrayList 的迭代器是'快速失败'(fail-fast)的。它内部维护了一个 modCount 变量,记录集合被修改的次数。当你开始遍历时,它会记下当前的 modCount 值作为预期值。如果在遍历过程中,集合的结构发生了改变(比如 add 或 remove),modCount 就会增加。下一次调用 next() 时,迭代器发现当前 modCount 不等于预期的值,就会直接抛出这个异常。
虽然文档里说这是单线程下遍历修改也会触发,但在多线程环境下,如果多个线程同时操作同一个非线程安全的集合,这种竞争条件更容易发生,而且更难复现。
怎么解决
别慌,有几种成熟的方案。
- 用线程安全的集合类。如果是读多写少,
CopyOnWriteArrayList是个好选择。它每次修改都复制新数组,保证迭代时的数据一致性。 - 如果是 Map,直接用
ConcurrentHashMap。 - 如果必须用普通 List,可以用
Collections.synchronizedList包装,但要注意迭代时要手动加锁。 - 或者在遍历前把数据拷贝一份,只遍历副本,原集合随便改。
代码示例
下面展示一下错误的写法,以及推荐的修正方式。
// 错误示范:遍历中直接修改
List<String> list = new ArrayList<>();
// ... 填充数据
for (String s : list) {
if (shouldRemove(s)) {
list.remove(s); // 这里会抛异常
}
}
修正后的写法:
// 推荐:使用 Iterator 移除
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String s = it.next();
if (shouldRemove(s)) {
it.remove(); // 安全移除
}
}
如果是多线程环境,建议直接换容器:
List<String> safeList = new <>();

