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

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


```java
 public static void main(String[] args) {
        List 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 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异常。

过程如下图:
遍历List集合和Map进行修改和删除报java.util.ConcurrentModificationException错误详解_第1张图片
三、异常的解决
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的迭代删除是一样的

遍历List集合和Map进行修改和删除报java.util.ConcurrentModificationException错误详解_第2张图片
遍历List集合和Map进行修改和删除报java.util.ConcurrentModificationException错误详解_第3张图片
遍历List集合和Map进行修改和删除报java.util.ConcurrentModificationException错误详解_第4张图片

你可能感兴趣的:(list,java,python)