GVKun编程网logo

在遍历和删除ArrayList中的元素时如何避免java.util.ConcurrentModificationException

12

在本文中,我们将给您介绍关于在遍历和删除ArrayList中的元素时如何避免java.util.ConcurrentModificationException的详细内容,此外,我们还将为您提供关于Ar

在本文中,我们将给您介绍关于在遍历和删除ArrayList中的元素时如何避免java.util.ConcurrentModificationException的详细内容,此外,我们还将为您提供关于ArrayList remove时报ConcurrentModificationException、ArrayList中ConcurrentModificationException、ArrayList中foreach循环中增添、删除导致ConcurrentModificationException、ArrayList中的ConcurrentModificationException,并发修改异常,fail-fast机制。的知识。

本文目录一览:

在遍历和删除ArrayList中的元素时如何避免java.util.ConcurrentModificationException

在遍历和删除ArrayList中的元素时如何避免java.util.ConcurrentModificationException

我有一个要迭代的ArrayList。遍历它时,我必须同时删除元素。显然,这引发了java.util.ConcurrentModificationException

解决此问题的最佳实践是什么?我应该先克隆列表吗?

我删除的不是循环本身的元素,而是代码的另一部分。

我的代码如下所示:

public class Test() {    private ArrayList<A> abc = new ArrayList<A>();    public void doStuff() {        for (A a : abc)         a.doSomething();    }    public void removeA(A a) {        abc.remove(a);    }}

a.doSomething可能打电话Test.removeA();

答案1

小编典典

两种选择:

  • 创建要删除的值的列表,将其添加到循环中的列表中,然后originalList.removeAll(valuesToRemove)在最后调用
  • remove()在迭代器本身上使用该方法。请注意,这意味着你不能使用增强的for循环。
    作为第二个选项的示例,从列表中删除任何长度大于5的字符串:
List<String> list = new ArrayList<String>();...for (Iterator<String> iterator = list.iterator(); iterator.hasNext(); ) {    String value = iterator.next();    if (value.length() > 5) {        iterator.remove();    }}

ArrayList remove时报ConcurrentModificationException

ArrayList remove时报ConcurrentModificationException

ArrayList remove时报ConcurrentModificationException:

ConcurrentModificationException 这错误本身是为“提醒”程序员正在并发【修改Arraylist数据和循环读取Arraylist数据结构】, 因为ArrayList 不是写线程安全的数据结构,所以会报ConcurrentModificationException, Arraylist不是一个同步循环和删除为目的数据结构。


如果非要删除ArrayList里的元素,可使用iterator

  1. Iterator<String> iter = list.iterator();  

  2. while(iter.hasNext()){  

  3.     String s = iter.next();  

  4.     if(s.equals("xxx")){  

  5.         iter.remove();  

  6.     }  

  7. }  

的方式删除。

ArrayList中ConcurrentModificationException

ArrayList中ConcurrentModificationException

java中两种基本的集合结构ArrayList和LinkedList底层有两种不同的存储方式实现,ArrayList为数组实现,属于顺序存储,LinkedList为链表实现,属于链式存储,在对ArrayList做迭代删除时,会出现ConcurrentModificationException

public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        list.add("aa");

        Iterator<String> iterator = list.iterator();
        while (iterator.hasNext()) {
            String next = iterator.next();
            list.remove(next);
        }
    }
Exception in thread "main" java.util.ConcurrentModificationException
	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
	at java.util.ArrayList$Itr.next(ArrayList.java:859)
	at com.baicells.linked.list.ListTest.main(ListTest.java:23)

但如果在list再添加一个元素,如bb,此时list.size = 2,上述代码运行结束后,list中只有bb,虽然与预期结果不一致,但并没有出现ConcurrentModificationException,当再次向集合中添加更多元素时,又出现了ConcurrentModificationException,使用的jdk版本为1.8.

ArrayList源码中,方法iterator()返回了Itr对象

/**
     * Returns an iterator over the elements in this list in proper sequence.
     *
     * <p>The returned iterator is <a href="#fail-fast"><i>fail-fast</i></a>.
     *
     * @return an iterator over the elements in this list in proper sequence
     */
    public Iterator<E> iterator() {
        return new Itr();
    }

Itr为ArrayList内部类,主要关注hasNext,next,checkForComodification方法

/**
     * An optimized version of AbstractList.Itr
     */
    private class Itr implements Iterator<E> {
        int cursor;       // index of next element to return
        int lastRet = -1; // index of last element returned; -1 if no such
        int expectedModCount = modCount;

        Itr() {}

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

        @SuppressWarnings("unchecked")
        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];
        }

        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();
            }
        }

        @Override
        @SuppressWarnings("unchecked")
        public void forEachRemaining(Consumer<? super E> consumer) {
            Objects.requireNonNull(consumer);
            final int size = ArrayList.this.size;
            int i = cursor;
            if (i >= size) {
                return;
            }
            final Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length) {
                throw new ConcurrentModificationException();
            }
            while (i != size && modCount == expectedModCount) {
                consumer.accept((E) elementData[i++]);
            }
            // update once at end of iteration to reduce heap write traffic
            cursor = i;
            lastRet = i - 1;
            checkForComodification();
        }

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

modCount在add和remove时都会执行++操作,这个可在ArrayList源码中找到出处

在Ite类中,将expectedModCount 的大小初始化为modCount,当执行hashNex和nex时,都不会使modCount的值发生变化

当list中只有一个数据时,此时cursor=0,size=1,hasNex返回true,在next方法中校验modCount和expectedModCount是否相等,如果不相等,则抛出并发修改异常,如果相等,对cursor做了+1操作

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

此时两者是相等的,都为1,然后执行remove操作删除该数据

public boolean remove(Object o) {
        if (o == null) {
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    fastRemove(index);
                    return true;
                }
        } else {
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }

    /*
     * Private remove method that skips bounds checking and does not
     * return the value removed.
     */
    private void fastRemove(int index) {
        modCount++;
        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work
    }

remove是对modCount做了++操作,并且对size做了--操作,while循环继续,hasNext,cursor=1,size=0,两者不相等,则进入到循环,执行next操作

此时modCount在上述remove操作时已经做了++操作,expectedModCount的值却没有变化,故modCount和expectedModCount是不相等的,因此抛出ConcurrentModificationException

当输入两个参数时为什么就不会报错了,虽然结果不是预期的清空了集合?

当集合为2时,在第一次删除后,各关键属性值分别为cursor=1,modCount在add两次后变为2,在remove一次后变为3,expectedModCount=2,size在remove后--,变为1,故此时在while循环hasNext中对比cursor!=size,返回false,while循环结束,继续向下走,所以最后集合中剩下了第二次add的结果,第一次add结果被删除,程序也没有出现ConcurrentModificationException异常。

当输入三个参数或者更多时,会怎样?

继续按照上述思路分析,当集合中有三个元素时,在第一次删除后,各关键属性值分别为cursor=1,modCount在add三次后变为3,在remove一次后变为4,expectedModCount=3,size在remove后--,变为2,此时while循环中cursor!=size返回true,进入while循环,next方法中检测到modCount != expectedModCount返回false,则抛出ConcurrentModificationException

当为更多元素时,在第二次进入到next方法后,都将抛出ConcurrentModificationException,只有在数组元素个数为2时,才不会发生ConcurrentModificationException,但结果也不是我们预期的

 

综上,不要在迭代集合时删除元素,即使是foreach或者普通for循环(普通for循环或者foreach也会造成size--),也不要这么做,这样做可能造成我们意想不到错误。

 

ArrayList中foreach循环中增添、删除导致ConcurrentModificationException

ArrayList中foreach循环中增添、删除导致ConcurrentModificationException

一、使用背景

在阿里巴巴开发手册中,有这样一条规定:不要在foreach循环里进行add和remove操作(这里指的是List的add和remove操作),否则会抛出ConcurrentModificationException。remove元素请使用iterator。

f29f0bec6c933e9d6fb3fb414219ef4.png

二、源码

1.我们知道foreach是语法糖,他本质还是iterator进行的循环,因此下面的代码和使用foreach循环是一样的。在循环里面我们使用“错误”操作,使用List的add方法进行操作,会抛出ConcurrentModificationException

       ArrayList<String> arrayList = new ArrayList<>();
        arrayList.add("apple");
        Iterator<String> iterator = arrayList.iterator();
        while(iterator.hasNext()){
            String value = iterator.next();
            if("apple".equals(value)){
                arrayList.add("orange");
            }
        }

三、源码解析

1.arrayList.iterator();

①返回Itr类,并将modcount的值赋值给一个变量expectedModCount,其中modcount表示List实际被增删的次数,expectedModCount表示该迭代器期望被增删的次数,当新建Itr类的时候会给他赋初始值,只有通过该迭代器进行值的增删才会修改该值

1f94f5447debd05c92a2ce77cf2bf53.png

0f9094193aa7672ddd5392485494233.png

2.iterator.next();

①在调用迭代器的next方法时,他会进行检查,比较modCount和expectedModCount的值,如果不相等,Concurrent

a06c77dae6f16bf2838a90466954f5c.png

05b5c8ca957494affd4157961ac3763.png

四、总结

1.modCount和expectedModeCount不一致才会抛出ConcurrentModificationException。当我们调用List的remove方法时,他只会修改modCount的值;当我们调用iterator的remove方法,他会将modCount的值赋值给expectedModeCount

2.modCount和expectedModeCount是属于fast-fail机制,用于多线程中,当进行遍历的时候,有其他线程修改值的时候就会进行检查

五、解决方法

1.使用普通for循环进行操作

2.在循环中使用iterator进行操作

3.使用stream流进行过滤

4.使用fast-saft安全的类,如ConCurrentLinkedQueue

ArrayList中的ConcurrentModificationException,并发修改异常,fail-fast机制。

ArrayList中的ConcurrentModificationException,并发修改异常,fail-fast机制。

一:什么时候出现?

  当我们用迭代器循环list的时候,在其中用list的方法新增/删除元素,就会出现这个错误。

package com.sinitek.aml;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class ConcurrentModificationExceptionDemo {


    public static void main(String[] args) {


        List<String> arrayList = new ArrayList<>();

        arrayList.add("a");
        arrayList.add("b");
        arrayList.add("c");


        Iterator<String> iterator = arrayList.iterator();
        while(iterator.hasNext()){
            String tmp = iterator.next();
            if ("a".equals(tmp)){
                arrayList.remove(tmp);

            }
        }




    }
}

 

二:为什么会出这个错误? 

 如上图所示,运行代码就会有这个错误,我们看一下堆栈。

Exception in thread "main" java.util.ConcurrentModificationException
	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
	at java.util.ArrayList$Itr.next(ArrayList.java:859)
	at com.sinitek.aml.ConcurrentModificationExceptionDemo.main(ConcurrentModificationExceptionDemo.java:22)

  

  可以看到,是在ArrayList里面的Itr内部类里的checkForModification()方法里,抛出的这个异常,我们看下源码。

final void checkForComodification() {
            //如果修改的次数 和 预期修改的次数不一致,就会报错
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }

 

  可以看到,上面两个关键的值是:modCount和expectModCount,其中的modCount来自ArrayList的父类,AbstractList,有兴趣的可以看下上面的翻译,也说明了这个字段的作用,正是为了fail-fast(快速失败)设计的。

  该值会在new出对象的时候,初始化为0;并且在每次的修改/删除操作时自增。

   

//arrayList的add方法,会先调用一次检查容量是否够的操作,我们可以看到,jdk专门在里面加了注释,会 Increments modCount, 增加modCount的值
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

    //确保新增元素容量大小够用的方法
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++; //首先先自增一次
    
        // overflow-conscious code
        if (minCapacity - elementData.length > 0)   //判断最小需要的容量是否大于数组的长度(这里有个疑问,为什么一定相减然后判断是否>0,直接比较会有溢出的问题需要考虑么)
            grow(minCapacity);  //扩容
    }

    //删除方法,也会有modCount++的操作
    public E remove(int index) {
        rangeCheck(index);

        modCount++;
        E oldValue = elementData(index);

        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                    numMoved);
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
    }

  

那我们看一下,expectedModCount时怎么变化的。

  在ArrayList的内部类Itr(这个命名是不是有点随意了)里面,会看到expectedModCount是作为成员变量一起初始化的,所以我们在最开始的时候,调用list.iterator()方法的时候,就会初始化expectedModCount,将他变成和modCount一样的值。所以问题出在:

    1. new ArrayList,新增后,modCount会有一个初始值0。

    2. 调用iterator()方法后,会把expectedModCount的值初始化为modCount的值,此时,这两个值是一致的。

    3. 如果在循环里,使用list对象新增/删除对象(本文最上面所作的操作),modCount就会自增变化,而此时,expectedModCount却不会改变。

    4. 调用iterator里面的next() -> checkForComodification(),判断modCount==expectModCount的时候,就会发现不一致,从而抛出这个异常。

 

三:怎么解决。

  有两种方法。

    • 使用Itr类中的remove方法移除,但是如果用的是从Iterable继承的iterator返回的Iterator是没有add()方法的接口的,如果需要add(),那么需要调用list专有的迭代器listInterator()方法返回的ListIterator().
    • 不适用迭代器,使用原生的循环方式,这样就可以避免这个异常。(增强for只是一个语法糖(Syntactic Sugar)本质上还是使用了迭代器,也会有这个异常)。

 

四:为什么要定义这个异常?

    凡事多问一个为什么,jdk为什么要怎么设计,很简单,其实从命名上就能看出来,当我们在循环的时候,无论是我们在迭代循环的时候(目前我们就是这种情况),或者是其他的线程修改了这个(这个应该是jdk主要想解决的),都会出现数据不一致的情况,所以需要这个异常,让你fail-fast,早点异常早点再次尝试等。

  Q:为什么不在add()方法的时候,同时修改expectModCount,这样就他们一致了不就行了?

  A:add()方法在调用的时候,有可能没有这个Itr这个对象,而且单单修改值是没有用的,需要把对应的迭代器内部保存数据的值也对应的修改,只修改expectModCount有点类似掩耳盗铃。

  Q:那jdk可以新建一个重载方法,让itr对象当作参数,把他传进来,修改对应的保存数据的数组和对应的expectModCount,这样就能保证两个都一致了把。

  A:确实,这种方法对于上文这种fail-fast确实适用,但是还有一种情况就是:可能修改list的线程和迭代的线程都不是同一个,所以根本就没有itr这个对象,也就无法修改这个值了。

今天的关于在遍历和删除ArrayList中的元素时如何避免java.util.ConcurrentModificationException的分享已经结束,谢谢您的关注,如果想了解更多关于ArrayList remove时报ConcurrentModificationException、ArrayList中ConcurrentModificationException、ArrayList中foreach循环中增添、删除导致ConcurrentModificationException、ArrayList中的ConcurrentModificationException,并发修改异常,fail-fast机制。的相关知识,请在本站进行查询。

本文标签: