遍历List集合和Map进行修改和删除报java.util.ConcurrentModificationException错误详解

news/2024/7/7 19:05:43

一、异常产生
当我们使用foreach迭代一个ArrayList或者HashMap时,如果尝试对集合做一些修改操作(例如删除元素或新增),可能会抛出java.util.ConcurrentModificationException的异常。


```java
 public static void main(String[] args) {
        List<User> list=new ArrayList<>();
        for(int i=0;i<10;i++){
            User user = new User();
            user.setMsg("123"+i);
            user.setName("王总"+i);
            list.add(user);
        }
        list.forEach(item->{
            if(Objects.equals(item.getMsg(),"1234")){
                User user = new User();
                item.setName("456789");
                CglibUtil.copy(item,user);
               list.add(user);
            }
        });
        System.out.println(list);
    }

执行之后会报:
在这里插入图片描述


map的例子:

```java
 jcItemMap.forEach((x,items)->{
            List<FinFreightItemR> finFreightItemRList = items.stream()
                    .filter(item -> Objects.equals(item.getAmountFlag(), FinConstant.YesOrNo.YES)).collect(Collectors.toList());
            if(CollectionUtil.isEmpty(finFreightItemRList)){
                jcItemMap.remove(x);
                allItemMap.remove(x);
            }
        });

在这里插入图片描述

二、java.util.ConcurrentModificationException异常产生的原因
ArrayList的父类AbstarctList中有一个域modCount,每次对集合进行修改(增添元素,删除元素。。。)时都会modCount++.而foreach的背后实现原理其实就是Iterator,等同于注释部分代码。在这里,迭代ArrayList的Iterator中有一个变量expectedModCount,该变量会初始化和modCount相等,但如果接下来对集合进行修改,modCount改变,就会造成expectedModCount !=modCount,此时就会掏出异常java.util.ConcurrentModificationException异常。

过程如下图:
在这里插入图片描述
三、异常的解决
1.单线程环境
上面我们已经了解了异常的发送原因,接下我们说一下解决方案。
1.1我们可以使用iterator迭代器进行遍历

 Iterator<User> iterator = list.iterator();
        while(iterator.hasNext()){
            User user = iterator.next();
            if(Objects.equals(user.getMsg(),"1234")){
                iterator.remove();
            }
        }
        System.out.println(list);

细心的朋友会发现Itr中的也有一个remove方法,实质也是调用了ArrayList中的remove,但增加了expectedModCount = modCount;保证了不会抛出java.util.ConcurrentModificationException异常。

但是,这个办法的有两个弊端
1.只能进行remove操作,add、clear等Itr中没有。
2.而且只适用单线程环境。

2、多线程环境
方法一:迭代前加锁,解决了多线程问题,但还是不能进行迭代add、clear等操作。

public class Test12 {
    static List<String> list = new ArrayList<String>();

    public static void main(String[] args) {
        list.add("a");
        list.add("b");
        list.add("c");
        list.add("d");

        new Thread() {
            public void run() {
                Iterator<String> iterator = list.iterator();

                synchronized (list) {
                    while (iterator.hasNext()) {
                        System.out.println(Thread.currentThread().getName()
                                + ":" + iterator.next());
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        }
                    }
                }
            };
        }.start();

        new Thread() {
            public synchronized void run() {
                Iterator<String> iterator = list.iterator();

                synchronized (list) {
                    while (iterator.hasNext()) {
                        String element = iterator.next();
                        if (Objects.equals(element,"c")) {
                            System.out.println(Thread.currentThread().getName()
                                    + ":" + element);
                            iterator.remove();
                        }
                    }
                }
            };
        }.start();
    }
}

方法二:采用CopyOnWriteArrayList,解决了多线程问题,同时可以add、clear等操作

public class Test12 {
    static List<String> list = new CopyOnWriteArrayList<>();

    public static void main(String[] args) throws InterruptedException {
        list.add("a");
        list.add("b");
        list.add("c");
        list.add("d");
        new Thread() {
            public void run() {
                Iterator<String> iterator = list.iterator();

                synchronized (list) {
                    while (iterator.hasNext()) {
                        System.out.println(Thread.currentThread().getName()
                                + ":" + iterator.next());
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        }
                    }
                }
            };
        }.start();

        new Thread() {
            public synchronized void run() {
                Iterator<String> iterator = list.iterator();

                synchronized (list) {
                    while (iterator.hasNext()) {
                        String element = iterator.next();
                        if (Objects.equals(element,"c")) {
                            System.out.println(Thread.currentThread().getName()
                                    + ":" + element);
                            list.remove(element);
                            list.add("123456");
                        }
                    }
                }
            };
        }.start();
        Thread.sleep(5000);
        System.out.println(list);
    }
}

CopyOnWriteArrayList也是一个线程安全的ArrayList,其实现原理在于,每次add或remove等所有的操作都是重新创建一个新的数组,再把引用指向新的数组。

对于HashMap的迭代删除是一样的

在这里插入图片描述

在这里插入图片描述


http://lihuaxi.xjx100.cn/news/1771623.html

相关文章

解决方案 |法大大电子合同推动汽车后市场多元数智化发展

近日&#xff0c;商务部等9部门联合发布《关于推动汽车后市场高质量发展的指导意见》&#xff08;以下简称《指导意见》&#xff09;&#xff0c;明确了汽车后市场发展的总体目标和主要任务&#xff0c;系统部署推动汽车后市场高质量发展&#xff0c;促进汽车后市场规模稳步增长…

【使用python绘制统计图表】

在Python中&#xff0c;我们通常使用matplotlib&#xff0c;pandas&#xff0c;和seaborn等库来绘制统计图表。 A.以下是一些示例&#xff1a; 首先&#xff0c;确保你已经安装了这些库。如果没有&#xff0c;可以使用以下命令进行安装&#xff1a; pip install matplotlib …

浅谈插接母线温度在线监测系统研究与应用-安科瑞黄安南

摘要 低压封闭式插接母线是供配电设施的关键部件&#xff0c;安装在生产车间内部高空&#xff0c;不易保养和维护&#xff0c;在安装不良或保养不当时易发生故障。插接点温度的异常变化与母线故障的发生有着密切的关系&#xff0c;以汽车整车制造工厂为例&#xff0c;提出母线接…

蓝桥杯国一,非ACMer选手保姆级经验分享

目录 一、前言二、蓝桥杯简介三、0基础计算机新手小白&#xff0c;赛前如何准备提高自己的获奖率&#xff1f;3.1 每两周参加一次【蓝桥算法双周赛】3.2 多练真题3.3 参加每一场官方校内模拟赛 四、结语 一、前言 hello&#xff0c;大家好&#xff0c;我是大赛哥(弟)&#xff…

Jmeter 性能压测 —— 混合场景

性能测试&#xff0c;单场景的目的一般是为了发现缺陷、发现瓶颈。 完成所有单个重点场景的性能测试之后&#xff0c;还需要做一个混合场景的性能测试-评估系统整体性能。 1、场景设计 使用Jmeter 做混合场景设计 在一个测试计划&#xff0c;将每个重点测试场景各创建为一个…

中级经济师各专业通过率是多少

中级经济师整体通过率在15%左右&#xff0c;其中人力资源专业通过率37%左右&#xff0c;工商管理通过率25%左右&#xff0c;金融专业通过率16%&#xff0c;其他专业通过率比较低&#xff0c;这三个专业占到总合格人数的75%左右。通过率不高&#xff0c;很大原因是考试题目综合性…

【React入门实战】实现Todo代办

文章目录 效果功能-状态管理相关接口定义相关方法定义 UIinput输入框&#xff1a;回车添加todo标题列表列表项Main 总体代码 非常简单入门的react-todo练习&#xff0c;代码写的很小白。 效果 技术栈&#xff1a;react-typeScript 数据分为代办Todo和已办完Done&#xff0c;可…

js基础-数据类型检测

1、typeof关键字 返回一个字符串&#xff0c;作用于基本数据类型直接在计算机底层基于数据类型的值(二进制) 进行检测。typeof null "object" 对象存储在计算机中&#xff0c;都是以000 开始的二进制存储&#xff0c;null也是&#xff0c;所以检测出来的结果是对象…