ConcurrentModificationException---异常剖析

Exception in thread "main” java.util.ConcurrentModificationException
在对list集合进行遍历,并且remove的时候,有可能会提示该异常

结论

从异常栈来看的话,异常信息是在

final void checkForComodification() {
    
    
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}
这个方法中throw的,所以根本原因是:modCount != expectedModCount

简单而言一句话:

在iteraor循环遍历中,如果直接调用list的remove方法,只会将modCount++;而不会更新expectedModCount的值

如果调用的是iterator的remove方法,除了调用list的remove方法,还会把modCount的最新值赋值给expectedModCount

源码分析

public class ConcurrentModificationExceptionDemo {
    
    
    public static void main(String[] args) {
    
    
        List<String> list = new ArrayList<>();
        list.add("1");
        list.add("2");
        list.add("3");


        Iterator<String> iterator = list.iterator();
        while (iterator.hasNext()){
    
    
            if("2".equals(iterator.next())){
    
    
//                list.remove("2");
                list.remove(iterator.next());
//                iterator.remove();
            }
        }


        System.out.println(list.size());
    }
}

1、如果是list.remove(iterator.next());就会提示这个异常
2、如果是list.remove("2”); 或者 iterator.remove(); 就不会异常信息,但是这两个的原理不一样,后面再说

modCount:表示对数组的操作次数(remove、add都会+1)
expectedModCount:表示iterator对数组的操作次数(姑且先这么理解)

list.remove(iterator.next())

1.在调用这个方法的时候,会调用java.util.ArrayList#remove(java.lang.Object); 这里ArrayList的方法
这个remove方法,是根据object来从数组中依次过滤要删除的元素下标是多少,然后调用fastRemove

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
}

这里可以看到,在调用fastRemove的时候,会modCount++了;

这个方法的根本问题就是,在remove的时候,直接调用了ArrayList的remove方法,对modCount+1之后,没有同步把expectedModCount+1;

那在什么时候会报错呢?
在remove之后,再次去执行if判断的时候,if判断的时候会调用iterator.next();获取到数组的下一个值

iterator.next()

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];
}

可以看到,再次循环的时候,执行到iterator.next()的时候,会先调用checkForComodification();这时候,modCount 值是4,expectedModCount的值是3,所以就异常了;

iterator.remove();

public void remove() {
    
    
    // lastRet 这个参数可能是记录了当前遍历的元素对应的在数组中的下标(不确定,待验证)
    if (lastRet < 0)
        throw new IllegalStateException();
    checkForComodification();


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

这里是调用的iterator的remove方法,在方法中,又调用了list的remove方法
这里list的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;
}

list的remove在将数据从数组中移除之后,将modCount++;这里没什么问题,和上面的fastRemove是一样的操作,都是将数据从数组中移除

但是在iterator的remove方法中,调用了list的remove之后,会有这么一行代码

expectedModCount = modCount;
也就是:将最新一次remove操作之后的modCount值赋值给expectedModCount

所以,在下次继续遍历,调用iterator.next();此时,modCount和expectedModCount的值是一样的,就不会报错

list.remove(“2”);

前面我有说过,如果调用的是这个方法,那么在remove之后,也不会报错,但是:调用这个方法是有问题的

因为这里如果直接调用list.remove(“2”)的话,在进行下次循环的时候,会调用hasNext()判断是否需要继续遍历

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

如果直接使用list.remove(“2”);这里返回的是false,就直接退出遍历了,所以这里是有问题的

size:是当前数组的个数(长度)
Cursor:个人理解是当前iterator遍历的元素下标,在每次调用iterator.next()的时候,会+1

如果在遍历的时候,里面的几个变量是有关联的:

cursor:初始值为0,每调用一次next();就会+1;remove()方法中,会把lastRet的值赋值给cursor
lastRet:初始值为-1,每调用next()的时候,会把cursor加1之前的值,赋值给lastRet;调用remove()的时候,会把改值赋值为-1
size:当前数组的长度,remove之后,会更新

第三部分这里,list.remove(“2”) 这个我没有讲清楚,后面我再整理一下这里的逻辑,更新上去

所以,在对集合进行遍历的时候,remove操作也要用iterator的,通俗的讲,就是要配套使用

猜你喜欢

转载自blog.csdn.net/CPLASF_/article/details/108567244