ConcurrentModificationException---Analysis of exception

Exception in thread "main" java.util.ConcurrentModificationException
may prompt the exception when traversing the list collection and removing

in conclusion

From the perspective of the exception stack, the exception information is in

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

In a nutshell:

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

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

Source code analysis

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. If it is list.remove(iterator.next()); it will prompt this exception.
2. If it is list.remove("2”); or iterator.remove(); there will be no exception information, but these two The principle is different, I'll talk about it later

modCount: indicates the number of operations on the array (remove, add will be +1)
expectedModCount: indicates the number of operations on the array by the iterator (let's understand it first)

list.remove(iterator.next())

1. When calling this method, it will call java.util.ArrayList#remove(java.lang.Object); The method of ArrayList here,
the remove method, is to filter the elements to be deleted from the array according to the object. The subscript is How much, then call 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
}

Here you can see that when fastRemove is called, modCount++ will be used;

The fundamental problem of this method is that when removing, the remove method of ArrayList is directly called. After modCount+1, the expectedModCount+1 is not synchronized;

When will an error be reported?
After remove, when the if judgment is executed again, iterator.next() will be called when the if judgment is made; the next value of the array is obtained

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

It can be seen that when the loop is executed again, when iterator.next() is executed, checkForComodification() will be called first; at this time, the value of modCount is 4 and the value of expectedModCount is 3, so it is abnormal;

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

After the remove of the list removes the data from the array, modCount++; there is no problem here, it is the same operation as the fastRemove above, it is to remove the data from the array

But in the remove method of iterator, after calling the remove of list, there will be such a line of code

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

Therefore, the next time you continue to traverse, call iterator.next(); at this time, the values ​​of modCount and expectedModCount are the same, and no error will be reported.

list.remove(“2”);

I said earlier that if this method is called, then no error will be reported after remove, but: calling this method is problematic

Because if you call list.remove("2") directly here, when the next loop is performed, hasNext() will be called to determine whether you need to continue traversing

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

If you directly use list.remove("2"); here is false, you will exit the traversal directly, so there is a problem here

size: the number of the current array (length)
Cursor: personal understanding is the element index traversed by the current iterator, it will be +1 each time iterator.next() is called

If during traversal, several variables inside are related:

cursor: the initial value is 0, every time next() is called; it will be +1; in the remove() method, the value of lastRet will be assigned to the cursor
lastRet: the initial value is -1, every time next() is called, it will Add the value before the cursor by 1 and assign it to lastRet; when you call remove(), the changed value will be assigned to -1
size: the length of the current array, after the remove, it will be updated

In the third part, I didn’t make it clear about list.remove("2"). I will sort out the logic here and update it.

Therefore, when traversing the collection, the remove operation should also use iterator. In layman's terms, it should be used in conjunction

Guess you like

Origin blog.csdn.net/CPLASF_/article/details/108567244