当前位置: 首页 > web >正文

ConcurrentModificationException 并发修改异常详解

一、异常基本概念

ConcurrentModificationException 是 Java 开发中常见的运行时异常,属于 java.util 包。它通常在集合遍历过程中被修改时抛出,无论是单线程还是多线程环境都可能发生。

二、异常发生的核心场景
  1. 单线程场景:在遍历集合时直接修改集合内容
  2. 多线程场景:一个线程遍历集合,另一个线程修改集合
三、底层机制:fail-fast 机制

Java 集合框架(如 ArrayList、HashMap 等)普遍采用 fail-fast 机制来检测并发修改,其核心原理是:

  • 每个集合内部维护一个 modCount 变量,记录集合的修改次数
  • 迭代器在创建时会保存当前的 modCount 到 expectedModCount
  • 每次迭代操作(如 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");

        

        // 错误做法:遍历过程中直接删除元素

        for (String item : list) {

            if ("B".equals(item)) {

                list.remove(item); // 这里会抛出ConcurrentModificationException

            }

        }

        

        // 正确做法:使用迭代器的remove方法

        Iterator<String> iterator = list.iterator();

        while (iterator.hasNext()) {

            String item = iterator.next();

            if ("B".equals(item)) {

                iterator.remove(); // 安全删除

            }

        }

        

        // Java 8+ 推荐做法:使用Stream过滤

        list = list.stream()

                .filter(item -> !"B".equals(item))

                .collect(java.util.stream.Collectors.toList());

    }

}

多线程场景示例

import java.util.ArrayList;

import java.util.List;

import java.util.concurrent.TimeUnit;

public class MultiThreadModificationDemo {

    private static List<String> list = new ArrayList<>();

    

    public static void main(String[] args) {

        list.add("A");

        list.add("B");

        list.add("C");

        

        // 线程1:遍历集合

        Thread reader = new Thread(() -> {

            for (String item : list) {

                try {

                    TimeUnit.MILLISECONDS.sleep(100);

                    System.out.println("读取元素: " + item);

                } catch (InterruptedException e) {

                    e.printStackTrace();

                }

            }

        });

        

        // 线程2:修改集合

        Thread writer = new Thread(() -> {

            try {

                TimeUnit.MILLISECONDS.sleep(300);

                System.out.println("删除元素: B");

                list.remove("B"); // 线程2修改集合,线程1会抛出异常

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

        });

        

        reader.start();

        writer.start();

    }

}

五、常见集合的并发修改行为
  1. ArrayList / LinkedList
    • 不支持并发修改,遍历时修改会触发 ConcurrentModificationException
    • 单线程中可通过迭代器 remove() 方法安全修改
  1. HashMap / HashSet
    • 同样基于 fail-fast 机制,遍历时修改会抛出异常
    • foreach 遍历、迭代器遍历、keySet()/values()/entrySet() 遍历时修改都会触发异常
  1. CopyOnWriteArrayList
    • 采用 fail-safe 机制,内部通过复制数组实现
    • 遍历时允许修改,修改会创建新数组,遍历使用旧数组
    • 适用于读多写少的场景,但写操作开销较大
  1. ConcurrentHashMap
    • Java 7 采用分段锁(Segment),Java 8 采用红黑树 + CAS
    • 支持并发读写,遍历过程中修改不会抛出异常
    • 是线程安全的高性能哈希表
六、解决方案与最佳实践
1. 单线程场景解决方案
  • 使用迭代器的 remove 方法

Iterator<String> iterator = list.iterator();

while (iterator.hasNext()) {

    String item = iterator.next();

    if (需要删除) {

        iterator.remove();

    }

}

  • 使用 Stream API 过滤(Java 8+):

list = list.stream()

        .filter(item -> 过滤条件)

        .collect(Collectors.toList());

  • 遍历前复制集合

for (String item : new ArrayList<>(list)) {

    // 操作item,不影响原集合

}

2. 多线程场景解决方案
  • 使用并发安全的集合
    • 列表:CopyOnWriteArrayList
    • 哈希表:ConcurrentHashMap
    • 集合:CopyOnWriteArraySet
  • 加锁保护

List<String> list = new ArrayList<>();

Object lock = new Object();

// 读操作

synchronized (lock) {

    for (String item : list) {

        // 遍历操作

    }

}

// 写操作

synchronized (lock) {

    list.add("新元素");

}

  • 使用 Collections.synchronizedXXX 包装

List<String> synchronizedList = Collections.synchronizedList(new ArrayList<>());

七、fail-fast 与 fail-safe 的区别

特性

fail-fast(如 ArrayList)

fail-safe(如 CopyOnWriteArrayList)

检测机制

遍历过程中检测集合修改

遍历使用集合的副本,不检测原集合修改

异常情况

发现修改立即抛出 ConcurrentModificationException

不会抛出异常,遍历旧数据

性能开销

开销小,无需复制集合

写操作需要复制集合,开销较大

适用场景

单线程或不允许并发修改的场景

多线程读多写少的场景

八、总结

ConcurrentModificationException 是 Java 集合框架中用于保护数据一致性的机制,本质是 fail-fast 策略的体现。避免该异常的核心原则是:

  1. 单线程环境:遍历时使用迭代器的 remove() 方法,或使用 Stream 过滤
  2. 多线程环境:使用并发安全的集合(如 CopyOnWriteArrayListConcurrentHashMap
  3. 理解集合的线程安全特性,根据场景选择合适的数据结构

掌握这些知识后,能有效避免并发修改异常,写出更健壮的 Java 代码。

http://www.xdnf.cn/news/13043.html

相关文章:

  • 用递归算法解锁「子集」问题 —— LeetCode 78题解析
  • 代码随想录算法训练营第60期第六十三天打卡
  • 华硕a豆14 Air香氛版,美学与科技的馨香融合
  • vue+cesium示例:3D热力图(附源码下载)
  • pycharm 设置环境出错
  • matlab时序预测并绘制预测值和真实值对比曲线
  • 浏览器指纹科普 | Do Not Track 是什么?
  • 2025 后端自学UNIAPP【项目实战:旅游项目】6、我的收藏页面
  • (14)-java+ selenium->元素定位大法之By xpath上卷
  • aurora与pcie的数据高速传输
  • 【从零学习JVM|第三篇】类的生命周期(高频面试题)
  • 自然语言处理——卷积神经网络
  • 你应该使用的 php 加解密函数
  • ELK实现nginx、mysql、http的日志可视化实验
  • centos7部署AWStats日志分析系统
  • java中word快速转pdf
  • Linux系统:进程间通信-匿名与命名管道
  • 离线语音识别方案分析
  • python3基础语法梳理
  • 行列视:企业数据分析新时代的利器(一)——深度解读与应用场景分析
  • 在Ubuntu中设置开机自动运行(sudo)指令的指南
  • 关于uniapp展示PDF的解决方案
  • UNECE R152——解读自动驾驶相关标准法规(AEB)
  • 论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(一)
  • 2025.06.09【读书笔记】|PromptBio:让生信分析更简单的AI平台
  • 逻辑回归暴力训练预测金融欺诈
  • docker 部署发现spring.profiles.active 问题
  • QT3D学习笔记——圆台、圆锥
  • Xcode 16.2 版本 pod init 报错
  • 关键领域软件测试的突围之路:如何破解安全与效率的平衡难题