其他分享
首页 > 其他分享> > ArrayList并发修改异常(forEach遍历,只能删除倒数第二个???)

ArrayList并发修改异常(forEach遍历,只能删除倒数第二个???)

作者:互联网

问题案例分析

public class ArrayListExceptionTest {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("1");
        list.add("2");
        list.add("3");
        /*for (String s : list) {
            if ("2".equals(s)){
                list.remove(s);
            }
        }*/
          for (String s : list) {
            if ("1".equals(s)){
                list.remove(s);
            }
        }
    }
}

结果1:我们发现删除“1”的时候会报一个并发修改的异常

Exception in thread "main" java.util.ConcurrentModificationException
	at java.util.ArrayList$Itr.checkForComodification(Unknown Source)
	at java.util.ArrayList$Itr.next(Unknown Source)
	at ArrayListExceptionTest.EqualUnderstand.main(EqualUnderstand.java:38)

结果2:当我们删除“2”的时候,我们发现可以正常删除,却不报错

当我们更换数组里的大小和内容时,我们发现使用ForEach遍历数组时,只能删除倒数第二个,删除其他则报错!!!这是什么原因呢?

ForEach底层使用迭代器进行遍历的,:它会先指针移动来判断是否当前的数组是否有值,再去获取下一个值

下面是伪代码:

while(array.haxNext()){
   int value = array.next();
}

步骤1:下面我们点击ArrayList的next()源码方法查看

根据next源码发现:

根据checkForComodification()源码发现:
我们可以知道当移动到下一位时,expectedModCount != ArrayList.this.modCount则抛出并发修改异常

public E next() {
    checkForComodification();  //检查是否需要抛出并发修改异常
    int i = cursor;           //cursor索引
    if (i >= size)             //size为当前数组的长度,如果cusor>size,则抛出没有下一个元素的异常
        throw new NoSuchElementException();
    Object[] elementData = ArrayList.this.elementData;  //获取当前数组
    if (i >= elementData.length)                        //如果cursor>数组.length,则提示并发修改异常 
        throw new ConcurrentModificationException();
    cursor = i + 1;                               //索引向前移动   cusor+1
    return (E) elementData[lastRet = i];     //lastRet:取出当前元素所在的索引
}
/*modCount:表示被删除的次数,expectedModCount:期待被删除的次数
  当迭代器初始化的时候expectedModCount == ArrayList.this.modCount,进行删除操作时,modCount+1
*/
final void checkForComodification() {
      if (expectedModCount != ArrayList.this.modCount)
            throw new ConcurrentModificationException();
 }

步骤2:查看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;
}

我们点击查看fastRemove方法

我们可以发现,每次执行remove方法时,会去调用fastRmove方法,会将modCount++,modCount为删除的次数

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
}

原理分析

  1. 所以每次当我们执行删除时,modCount+1,导致expectedModCount != ArrayList.this.modCount,抛出并发修改异常
  2. 只有当倒数第二个元素的时候,cusor==size时,认为是遍历结束,则执行next()来进行判断是否并发修改

流程图如下:

在这里插入图片描述

为什么使用iter迭代器删除,却不会报错呢?

案例:

public static void main(String[] args) {
    ArrayList<String> list = new ArrayList<>();
    list.add("1");
    list.add("2");
    list.add("3");
    Iterator<String> iterator = list.iterator();
    while (iterator.hasNext()){
        String s = iterator.next();
        if ("2".equals(s)){
            iterator.remove();
        }
    }
}

我们发现无论删除什么元素,都是不会报错的!!!!

我们点击remove查看源码:

我发现调用remove方法时,直接赋值修改·expectedModCount = modCount,这样在进行next()检查时,就不会抛出并发修改异常了!!

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

标签:index,遍历,ArrayList,list,remove,modCount,forEach,size
来源: https://blog.csdn.net/weixin_41340417/article/details/114764000