ArrayList循环遍历并删除元素出现并发修改异常

11 篇文章 1 订阅
5 篇文章 0 订阅

1. 第一种循环删除出现异常

import java.util.ArrayList;

/**
 * @author LanceQ
 * @version 1.0
 * @time 2021/4/28 19:16
 */
public class ListTest {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("1");
        list.add("2");
        list.add("3");
        for(String l:list){
            System.out.println(list.remove(l));//并发修改异常
        }

    }
}

循环删除第二个开始爆并发异常

true
Exception in thread "main" java.util.ConcurrentModificationException

  • 先了解一下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;
    }
  • 一般情况下程序的执行路径会走到else路径下,最终调用faseRemove方法:
    private void fastRemove(int index) {
        modCount++;   //AbstractList中的变量
        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
    }
  • 其上面错误产生的原因:其实foreach写法是对实际的Iterable、hasNext、next方法的简写,问题在上文的fastRemove方法中,可以看到第一行把modCount变量的值加一,但在ArrayList返回的迭代器(该代码在其父类AbstractList中):
        protected transient int modCount = 0;

        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();
            }
        }
        
        final void checkForComodification() {
            if (modCount != expectedModCount)      <----判断
                throw new ConcurrentModificationException();<--------这里异常
        }

        int expectedModCount = modCount;

这里会做迭代器内部修改次数检查,因为上面的remove(Object)方法修改了modCount的值,所以才会报出并发修改异常。要避免这种情况的出现则在使用迭代器迭代时(显示或for-each的隐式)不要使用ArrayList的remove,改为用Iterator的remove即可。

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

/**
 * @author LanceQ
 * @version 1.0
 * @time 2021/4/28 19:16
 */
public class ListTest {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("1");
        list.add("2");
        list.add("3");
        //错误的删除方法
//        for(String l:list){
//            System.out.println(list.remove(l));//并发修改异常
//        }
        System.out.println(list);
        Iterator<String> it = list.iterator();
        //这里判断的是下一个元素是否存在
        while (it.hasNext()){
            System.out.println(it.hasNext());
            //需要进行next操作才可以进行remove,
            // 否则会出现Java.lang.IllegalStateException异常(非法状态异常)
            it.next();
            it.remove();
        }
        System.out.println(list);
    }
}

运行结果
[1, 2, 3]
true
true
true
[]
  • Java.lang.IllegalStateException异常(非法状态异常)出现的原因是 删除了一个不满足条件的元素。通过Iterator来删除,首先需要使用next方法迭代出集合中的元素,然后才能调用remove方法,否则集合可能抛出java.lang.IllegalStateException异常。

  • 注意remove对象是否存在,如果这个记录已被remove掉,再次remove会出现此异常,容易出现在对同一对象(如List)做多次迭代remove的情景中。

2. 第二种循环删除不报异常,但是会出现有些数据没有删除的情况

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

/**
 * @author LanceQ
 * @version 1.0
 * @time 2021/4/28 19:16
 */
public class ListTest {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("1");
        list.add("2");
        list.add("3");
        list.add("4");
        list.add("5");
        remove2(list);
    }

    private static void remove2(ArrayList<String> list) {
        System.out.println(list);
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.remove(i));
        }
        System.out.println(list);
    }

}

运行结果
[1, 2, 3, 4, 5]
1
3
5
[2, 4]

这次的remove调用的是另一个remove方法

    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;
    }
  • 可以看到会执行System.arraycopy方法,导致删除元素时涉及到数组元素的移动。

  • 针对上方出现的运行结果与预想不一致,是由于在遍历第一个字符串时,因为符合删除条件,所以将该元素从数组中删除,并且将后一个元素移动(也就是第二个字符串b)至当前位置,导致下一次循环遍历时,后一个字符串并没有遍历到,所以无法删除。

针对这种情况可以倒序删除的方式来避免:

    private static void remove2(ArrayList<String> list) {
        System.out.println(list);
        for (int i = list.size() - 1; i >= 0; i--) {
            System.out.println(list.remove(i));
        }
        System.out.println(list);
    }
[1, 2, 3, 4, 5]
5
4
3
2
1
[]

3. 总结

  • 通过foreach方式进行删除的modCount变量的改变,会出现非法状态异常,可通过iterator迭代器的方式进行判断,删除。

  • 通过for循环变量list的长度,正序来进行list中元素的移除,会出现漏删除的情况,可通过倒序删除的方式来解决。

参考:https://www.cnblogs.com/huangjinyong/p/9455163.html

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Arraylist和HashMap在遍的时候插入数据会引起并发修改异常,因为它们两个都是不同步的,当其他线程在迭代器上修改元素时,会造成冲突。因此,如果要在遍的时候插入元素,建议使用线程安全的集合,比如CopyOnWriteArrayList和ConcurrentHashMap。 ### 回答2: 当ArrayList和HashMap正在遍时插入元素会导致两种不同的问题。 对于ArrayList来说,当我们在遍ArrayList时插入元素,会导致ConcurrentModificationException(并发修改异常)的抛出。这是因为ArrayList在遍时使用一个迭代器(Iterator)来获取元素,并且在每次调用迭代器的next()方法时都会检查modCount(修改次数)是否与预期一致,如果不一致则抛出异常。而插入元素会增加modCount的值,导致modCount与预期不一致,从而引发异常。 对于HashMap来说,当我们在遍HashMap时插入元素,不会抛出ConcurrentModificationException异常,但会导致遍过程出现不可预测的结果,可能会漏掉一些元素或者重复遍某些元素。这是因为HashMap的遍过程是通过entrySet()方法返回的迭代器来实现的,而且插入元素可能会导致HashMap的结构发生变化,从而影响迭代器的操作。在HashMap的实现中,如果在遍过程中发现结构发生变化,会通过fail-fast机制立刻抛出ConcurrentModificationException异常,但并不是所有情况下都能及时检测到结构变化。 因此,在遍ArrayList和HashMap时,最好避免在遍过程中插入元素,可以通过在遍前或者遍后进行元素的插入操作,或者选择使用线程安全的数据结构来避免这个问题。 ### 回答3: 在使用ArrayList和HashMap时,如果在遍的过程中进行插入操作,可能会导致遍出现问题。 对于ArrayList,当我们使用迭代器或者for循环进行遍时,如果在遍过程中进行插入操作,可能会导致ConcurrentModificationException(并发修改异常)的出现。这是因为ArrayList在进行插入操作时,会修改内部的modCount(修改次数)属性,而迭代器或者for循环会在每次迭代时检查modCount的值是否一致,如果不一致则抛出该异常。 对于HashMap,同样在使用迭代器或者for-each循环进行遍时,如果在遍过程中进行插入操作,可能会导致ConcurrentModificationException的出现。HashMap的遍是基于Entry数组实现的,当进行插入操作时,会导致数组的大小发生改变或者原有的元素位置发生改变。而迭代器或者for-each循环在每次迭代时,都会通过比较modCount判断HashMap是否发生了结构性的改变,如果发生了改变则抛出该异常。 因此,在遍ArrayList和HashMap时,如果要进行插入操作,可以考虑使用Iterator的add方法或者使用扩展增强for循环,它们能够避免ConcurrentModificationException的异常出现。另外,如果需要并发操作,可以考虑使用并发集合类如CopyOnWriteArrayList和ConcurrentHashMap来避免出现并发修改异常

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值