Java的ConcurrentModificationException异常介绍和解决方案

简介: ConcurrentModificationException(CME)是Java中一种快速失败(fail-fast)机制,当线程遍历集合时,若集合结构被修改(如add、remove),则抛出该异常。其原理基于`modCount`与`expectedModCount`的不一致。常见于ArrayList、HashMap等非线程安全集合。解决方法包括:使用Iterator.remove()、CopyOnWriteArrayList、倒序遍历、removeIf()或并发容器如ConcurrentHashMap,以避免并发修改问题。

关于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();

解决方案:

使用并发集合:

  1. ConcurrentHashMap
  2. CopyOnWriteArrayList
  3. ConcurrentLinkedQueue
  4. ConcurrentSkipListMap

Fail-fast是什么

指的是程序在运行代码的过程中,如果遇到错误或者异常状态,立即抛出异常停止运行,避免在后续的操作中引发更严重的数据不一致

相关文章
|
7月前
|
存储 Java 容器
java的线程池学习
线程池通过复用线程减少创建销毁开销,提升性能与响应速度。核心参数包括核心/最大线程数、队列、拒绝策略等,支持自定义配置与管理。适用于高并发场景,结合SpringBoot可实现异步任务处理,提高系统稳定性与效率。
209 2
|
机器学习/深度学习 算法 数据挖掘
神经网络训练失败的原因总结 !!
神经网络训练失败的原因总结 !!
660 0
|
7月前
|
存储 机器学习/深度学习 人工智能
基于反馈循环的自我进化AI智能体:原理、架构与代码实现
自我进化智能体突破传统AI静态局限,通过“执行-反馈-调整”闭环,实现持续自主优化。它结合大模型与在线学习,利用多评分器反馈自动改进提示或参数,无需人工干预。适用于医疗、金融、编程等动态场景,推动AI迈向终身学习。
1303 12
基于反馈循环的自我进化AI智能体:原理、架构与代码实现
|
6月前
|
弹性计算 Linux 网络安全
阿里云服务器 ECS 购买新手指南:小白也能懂的详细图文流程
对于新手而言,首次购买阿里云服务器 ECS 可能会因配置选项繁多而困惑。本文以自定义购买为核心,从入口选择到最终下单,详细拆解付费类型、地域、实例规格等关键配置步骤,并结合实际场景给出选型建议,帮助小白轻松完成 ECS 选购与部署。
|
Java 调度 Spring
Spring之定时任务基本使用篇
本文介绍了在Spring Boot项目中使用定时任务的基本方法。主要通过`@Scheduled`注解实现,需添加`@EnableScheduling`开启定时任务功能。文中详细解析了Cron表达式的语法及常见实例,如每秒、每天特定时间执行等。此外,还探讨了多个定时任务的执行方式(并行或串行)及其潜在问题,并留待后续深入讨论。
901 64
|
数据采集 机器学习/深度学习 算法
Python基于决策树多分类模型实现水色图像的水质评价
Python基于决策树多分类模型实现水色图像的水质评价
|
Java 测试技术 Spring
Spring-AOP
Spring-AOP
212 0
|
算法 芯片
基于51单片机点阵汉字显示程序设计
基于51单片机点阵汉字显示程序设计
基于51单片机点阵汉字显示程序设计
|
开发者
2024 乘风者计划全新启航!快来加入吧!
 2021年,阿里云开发者社区焕新升级,重磅推出“乘风者计划”!诚邀四海技术博主入驻社区,泼墨云间,书写天地。入驻社区,即可享丰厚权益! 新的一年,乘风者计划重磅升级!
252280 81
|
存储 安全 Java
Android 面试题及答案整理,最新面试题
Android 面试题及答案整理,最新面试题
731 2