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

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 代码。

相关文章:

  • 深度学习-1.神经网络理解
  • 博图 SCL 编程技巧:灵活实现上升沿与下降沿检测案例分享(下)
  • LangChain4j(18)——通过Xinference调用Rerank模型
  • 【前端实战】如何让用户回到上次阅读的位置?
  • 【C++】IO库 IO流
  • 禁用思科锐捷设备分页功能
  • redis--黑马点评--Redisson快速入门
  • pytorch卷积层权重之 二维互相关运算(corr2d) (亲测,已解决)
  • 神经网络学习-神经网络简介【Transformer、pytorch、Attention介绍与区别】
  • pikachu靶场通关笔记22-1 SQL注入05-1-insert注入(报错法)
  • 网页后端开发(基础1--maven)
  • 初探用uniapp写微信小程序遇到的问题及解决(vue3+ts)
  • 如何在 PyTorch 中自定义卷积核参数(亲测,已解决)
  • [免费]微信小程序问卷调查系统(SpringBoot后端+Vue管理端)【论文+源码+SQL脚本】
  • 设计模式-抽象工厂模式
  • C/Python/Go示例 | Socket Programing与RPC
  • 云原生时代的系统设计:架构转型的战略支点
  • GO语言---init函数
  • Go 语言底层(四) : 深入 Context 上下文
  • 鸿蒙 Stege模型 多模块应用
  • 南山老品牌网站建设/网站制作厂家有哪些
  • 代码源/什么是优化师
  • 沈阳做网站好的/百度学术论文查重入口
  • 中国建设银行网站首/宁德市安全教育平台
  • 网站制作合同注意事项/上海seo优化服务公司
  • 淘宝做网站价格/找文网客服联系方式