ConcurrentModificationException异常解析和解决方法

一、介绍

在Java开发过程中,使用iterator遍历集合的同时对集合进行修改就会出现java.util.ConcurrentModificationException异常,本文就以ArrayList为例去理解和解决这种异常。

二、单线程情况下问题分析及解决方案

2.1、抛出异常的代码

    /**
     * ConcurrentModificationException复现方法一
     */
    public static void listRemoveExcption(){
        ArrayList<Integer> arrayList = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            arrayList.add(Integer.valueOf(i));
        }
        // 复现方法一
        Iterator<Integer> iterator = arrayList.iterator();
        while (iterator.hasNext()) {
            Integer integer = iterator.next();
            if (integer.intValue() == 2) {
                arrayList.remove(integer);
            }
            System.out.println("---复现方法一:"+integer);
        }
    }

    /**
     * ConcurrentModificationException复现方法二
     */
    public static void listRemoveExcption2(){
        ArrayList<Integer> list = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            list.add(Integer.valueOf(i));
        }
        //复现方法二
        Iterator<Integer> it= list.iterator();
        for (Integer value : list) {
            Integer integer = it.next();
            if (integer.intValue() == 2) {
                list.remove(integer);
            }
            System.out.println("---复现方法二:"+integer);
        }
    }

2.2、问题分析及解决方案
复现方法一中:
使用Iterator遍历ArrayList, 抛出异常的是iterator.next()。看下Iterator next方法实现源码。
在这里插入图片描述
在next方法中首先调用了checkForComodification方法,该方法会判断modCount是否等于expectedModCount,不等于就会抛出java.util.ConcurrentModificationExcepiton异常。
在这里插入图片描述

我们接下来跟踪看一下modCount和expectedModCount的赋值和修改。
modCount是ArrayList的一个属性,继承自抽象类AbstractList,用于表示ArrayList对象被修改次数。

protected transient int modCount = 0;

整个ArrayList中修改modCount的方法比较多,有add、remove、clear、ensureCapacityInternal等,凡是设计到ArrayList对象修改的都会自增modCount属性。
源码如下:
在这里插入图片描述

在创建Iterator的时候会将modCount赋值给expectedModCount,在遍历ArrayList过程中,没有其他地方可以设置expectedModCount了,因此遍历该集合过程中expectedModCount会一直保持初始值5;

遍历的时候是不会触发modCount自增的,但是遍历到integer.intValue() == 2的时候,执行了一次arrayList.remove(integer),这行代码执行后modCount++变为了6,但此时的expectedModCount仍然为5。
在执行next方法时,遇到modCount != expectedModCount方法,导致抛出异常java.util.ConcurrentModificationException。

  • 复现方法二中:

在这里插入图片描述
在for循环中一开始也是对expectedModCount采用modCount进行赋值。
在进行for循环时每次都会有判定条件modCount == expectedModCount,
当执行完arrayList.remove(integer)之后,该判定条件返回false退出循环,
然后执行if语句,结果同样抛出java.util.ConcurrentModificationException异常。

这两种复现方法实际上都是同一个原因导致的

2.3 问题解决方案

    public static void listRemove(){
        ArrayList<Integer> arry = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            arry.add(Integer.valueOf(i));
        }
        //解决方法-
        Iterator<Integer> iterator = arry.iterator();
        while (iterator.hasNext()) {
            Integer integer = iterator.next();
            if (integer.intValue() == 2) {
                iterator.remove();
            }
            System.out.println("---解决方法:"+integer);
        }
    }

这种解决方案最核心的就是调用iterator.remove()方法。我们看看该方法源码为什么这个方法能避免抛出异常
在这里插入图片描述

在iterator.remove()方法中,同样调用了ArrayList自身的remove方法,但是调用完之后并非就return了,而是expectedModCount = modCount重置了expectedModCount值,使二者的值继续保持相等。

针对forEach循环并没有修复方案,因此在遍历过程中同时需要修改ArrayList对象,则需要采用iterator遍历。

上面提出的解决方案调用的是iterator.remove()方法,如果不仅仅是想调用remove方法移除元素,还想增加元素,或者替换元素,是否可以呢?浏览Iterator源码可以发现这是不行的,Iterator只提供了remove方法。

但是ArrayList实现了ListIterator接口,ListIterator类继承了Iter,这些操作都是可以实现的,使用示例如下:

    public static void listOperation(){
        ArrayList<Integer> arry = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            arry.add(Integer.valueOf(i));
        }
        
        ListIterator<Integer> iterator = arry.listIterator();
        while (iterator.hasNext()) {
            Integer integer = iterator.next();
            if (integer.intValue() == 5) {
                iterator.set(Integer.valueOf(6));
                iterator.remove();
                iterator.add(integer);
            }
        }
    }

三、 多线程情况下的问题分析及解决方案

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值