首页 > 其他分享 >ArrayList-ConcurrentModificationException异常分析记录

ArrayList-ConcurrentModificationException异常分析记录

时间:2022-12-27 19:56:23浏览次数:62  
标签:java 记录 ConcurrentModificationException ArrayList ite cursor modCount new

ConcurrentModificationException 记录与分析

public static void main(String[] args) {
    ArrayList<Integer> list = new ArrayList<>();
    list.add(1);
    Iterator<Integer> ite = list.iterator();
    while (ite.hasNext()) {
        Integer number = ite.next();
        if (number==1){
            list.remove(number);
        }
    }
}  

IDEA运行结果

Exception in thread "main" java.util.ConcurrentModificationException
at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1042)
at java.base/java.util.ArrayList$Itr.next(ArrayList.java:996)
at com.itheima.yuxi.TestConcurrentModificationException.main(TestConcurrentModificationException.java:12)  

分析异常位置在checkForComodification()方法

final void checkForComodification () {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }

所以我们从源码入手进一步分析其原因
Arraylist 没有iterator(),在Arraylist的父类找到查看

public Iterator<E> iterator() {
    return new Itr();
}

iterator()返回Itr()对象引用,接着我们查看Itr类的实现

private class Itr implements Iterator<E> {
    /**
     * 后续调用 next 返回的元素的索引
     */
    int cursor = 0;

    /**
     * 表示上一个访问的元素的索引
     */
    int lastRet = -1;

    /**
     * 表示对ArrayList修改次数的期望值,它的初始值为modCount。
     * modCount是AbstractList类中的一个成员变量
     * 
     */
    int expectedModCount = modCount;

    public boolean hasNext() {
        return cursor != size();
    }

    public E next() {
        checkForComodification();
        try {
            int i = cursor;
            E next = get(i);
            lastRet = i;
            cursor = i + 1;
            return next;
        } catch (IndexOutOfBoundsException e) {
            checkForComodification();
            throw new NoSuchElementException();
        }
    }

    public void remove() {
        if (lastRet < 0)
            throw new IllegalStateException();
        checkForComodification();

        try {
            AbstractList.this.remove(lastRet);
            if (lastRet < cursor)
                cursor--;
            lastRet = -1;
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException e) {
            throw new ConcurrentModificationException();
        }
    }

    final void checkForComodification() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }
}  

modCount作为一个成员变量,记录对list的修改次数,对集合的删除remove(int index)和新增add(int index, E element)都会modCount++;
protected transient int modCount = 0;

我们查看报错的代码,Iterator ite = list.iterator(); 获得Itr对象后,调用hasNext()查看是否有下一个元素,判断条件为cursor不等于size(集合大小),以下为源码

public boolean hasNext() {
        return cursor != size;
    }  

接下来调用next()方法查看索引为0的元素

public E next() {
        checkForComodification();
        int i = cursor;
        if (i >= size)
            throw new NoSuchElementException();
        Object[] elementData = ArrayList.this.elementData;
        if (i >= elementData.length)
            throw new ConcurrentModificationException();
        cursor = i + 1;
        return (E) elementData[lastRet = i];
    }  

首先调用checkForComodification()方法,cursor+1,接着将cursor-1的值赋给lastRet,最后获取到lastRet处的元素,初始值cursor为0,lastRet为-1,那么调用一次之后,cursor的值为1,lastRet的值为0,此时,modCount为1,expectedModCount也是1(执行add操作+1)..
接下来我们判断number是否为1,如果是则调用remove();

public boolean remove(Object o) {
    final Object[] es = elementData;
    final int size = this.size;
    int i = 0;
    found: {
        if (o == null) {
            for (; i < size; i++)
                if (es[i] == null)
                    break found;
        } else {
            for (; i < size; i++)
                if (o.equals(es[i]))
                    break found;
        }
        return false;
    }
    fastRemove(es, i);
    return true;
}  

fastRemove()对元素实际删除,modCount+1,集合长度-1,并将最后一个元素引用置为null以方便垃圾收集器进行回收工作。

private void fastRemove(Object[] es, int i) {
    modCount++;
    final int newSize;
    if ((newSize = size - 1) > i)
        System.arraycopy(es, i + 1, es, i, newSize - i);
    es[size = newSize] = null;
}  

此时各个变量的值:

对于iterator,其expectedModCount为1,cursor的值为1,lastRet的值为0.
对于list size为0,modCount为2

删除成功,调用while循环的hasNext()方法,cursor != size;继续调用next(),其方法的第一句:checkForComodification(); 抛出ConcurrentModificationException异常!!

final void checkForComodification() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }  

原因分析:list.remove()导致modCount与expectedModCount不同,注意:像使用for-each进行迭代实际上也会出现这种问题.

解决方案

单线程情况:
在ite类中存在一个remove方法

public void remove() {
        if (lastRet < 0)
            throw new IllegalStateException();
        checkForComodification();

        try {
            ArrayList.this.remove(lastRet);
            cursor = lastRet;
            lastRet = -1;
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }  

这个方法实际调用了ArrayList.this.remove()方法,但其中增加了一个操作

expectedModCount = modCount;  

所以,ArrayList删除元素要用迭代器的remove();

public static void main(String[] args) {
    ArrayList<Integer> list = new ArrayList<>();
    list.add(1);
    Iterator<Integer> ite = list.iterator();
    while (ite.hasNext()) {
        Integer number = ite.next();
        if (number==1){
            ite.remove();
        }
    }
}  

在以上单线程的情况下,查看多线程是否有影响

public static void main(String[] args) {
    ArrayList<Integer> list = new ArrayList<>();
    list.add(1);
    new Thread(new Runnable() {
        @Override
        public void run() {
            Iterator<Integer> ite = list.iterator();
            while (ite.hasNext()) {
                Integer number = ite.next();
                System.out.println(number);
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }).start();
    new Thread(new Runnable() {
        @Override
        public void run() {
            Iterator<Integer> ite = list.iterator();
            while (ite.hasNext()) {
                Integer number = ite.next();
                if (number == 1) {
                    ite.remove();
                }
            }
        }
    }).start();
}

IDEA运行结果

Exception in thread "Thread-0" java.util.ConcurrentModificationException
at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1042)
at java.base/java.util.ArrayList$Itr.next(ArrayList.java:996)
at com.itheima.yuxi.TestThreadCM$1.run(TestThreadCM.java:18)
at java.base/java.lang.Thread.run(Thread.java:835)  

多线程在修改集合和遍历集合时,通过iterator()访问,每个对象有不同的ite,可理解为expectedModCount 为每个线程私有,以上两个线程,线程1进行遍历,线程2进行修改,线程2改变后会导致expectedModCount 和 modCount的值自增,但是线程1expectedModCount没有自增,因此modCount != expectedModCount,抛出ConcurrentModificationException异常!!

给出两种解决方法

  1. 使用iterator()迭代时使用synchronized同步
  2. 使用并发容器CopyOnWriteArrayList(删除时用list.remove(number);有加锁操作)替代ArrayList和Vector

参考资料Matrix海子

标签:java,记录,ConcurrentModificationException,ArrayList,ite,cursor,modCount,new
From: https://www.cnblogs.com/OKGOsky/p/17008848.html

相关文章

  • 战斗录像工具开发记录
    把战况录像生成脚本,然后丢进引擎中去,然后就会回放录像了所谓录象,就是让引擎记录下所有输入的消息以及发生的时刻。由于游戏client可以严格按帧来......
  • JDK8引进的JVM参数变化记录
    1.PermGen空间​​被移除了,取而代之的是Metaspace​​需要做的调整为-XX:PermSize=64m-XX:MaxPermSize=128m变成 -XX:MetaspaceSize=64m-XX:MaxMetaspaceSize=128m否则......
  • 【记录贴】项目经理的进阶日常:靠年终总结获得了核心项目的机会
    进入项目经理这个岗位已经三年了,之前决定转行做项目经理是因为它涉及的知识面广,对个人的成长非常有帮助;也期望未来能积累一些大型且复杂的项目经验、获得更好的升职空间。......
  • linux安装pyarmor踩坑记录
    现有环境centos7.8python3.7.6pip20.0找度娘学习安装pyarmorpipinstallpyarmor然后查看版本pyarmor--version 进入pyarmor的执行路径cd/us......
  • K8S集群环境搭建记录
    使用kubeadm工具搭建K8S一主二从集群,一个master和两个node。环境初始化1.检查系统版本:Centos版本要在7.5或之上[root@K8SMASTER~]#cat/etc/redhat-releaseCentOS......
  • 试验记录
    试验记录......
  • 记录几个新发现的优秀CRM客户管理系统
    近期寻找CRM系统,前后试用了十几款,在这个过程中体验了一些名不符实的软件,也发现了一些优秀却不够知名的产品,特此记录几个以作备忘。 蓝点客户关系管理系统知名度不高,但......
  • yolo5使用gpu时遇到的问题记录
    一、问题描述:1、训练的时候提示不支持gpu2、使用如下命令检查为Falseimporttorchtorch.cuda.is_available()二、原因:pytorch版本的问题 三、解决办法: 重新安......
  • [虚树记录] CF613D Kingdom and its Cities
    这只蒟蒻看完题完全不会做,但是这只蒟蒻是通过百度搜索虚树找到这题的,发现这道CF*2800的题居然是许多人介绍虚树的第一道例题!我大概可以退役力!不过看完题解觉得真的还挺可......
  • 记录一次关于OpenCV的CmakeLists的探索
        编写基于OpenCV的图像处理程序,其中很重要的一道门槛就是编译OpenCV,应该说如果你对其中的内容如果不是很熟悉的话,即使是最简单粗暴的“两次configure,一次gene......