Analysis of the principle of java.util.ConcurrentModificationException thrown by the subList method in List

1. Start with the test code first:

public class Test {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        for (int i = 0;i<6000;i++){
            list.add(i);
        }
        List<Integer> list1 = list.subList(0,3000);
        List<Integer> list2 = list.subList(3000,6000);
        list2.clear();
        System.out.println("list1 = " + list1);
 
    }
}

First initialize a list of 6000 elements, then use list.subList() to intercept 3000 elements into list1, and then take out 3000 elements into list2, then clear list2, and finally print list1, and an exception will be thrown at this time :

2. Foreplay knowledge:

Principle analysis of subList() method:

Why does the above test method have this situation? It seems that there is no problem, but an exception is thrown when printing list1. It is definitely not possible that System.out.println() has a bug. Let's take a closer look at the code. It seems that there is only There will be a problem in the first few sentences of the print statement, then it is the call of subList() and the code of clear(), then where the problem occurs, let's find out;

Next, let's first look at the source code of the implementation of the subList() method in ArrayList to see what it does:

In the source code of the subList() method, subListRangeCheck(fromIndex, toIndex, size) is first called. The main function of this method is to determine whether the parameters passed in subList() are compliant. This is not the point, the point is that it returns new SubList(this, 0 , fromIndex, toIndex), returns a SubList object, continue to look at this SubList object, the source code is in line 1010:

As you can see from the source code, this SubList object is an internal class.

2.1. When constructing an object, 4 parameters are passed in:

AbstractList<E> parent: the list object that currently calls the subList() method

int offset: offset (starting from 0)

int fromIndex: start index (inclusive)

int toIndex: end index (not included)

2.2. Inside the constructor:

Assign the passed parent to the member variable parent of the SubList object;

fromIndex is assigned to the member variable parentOffset of the SubList object;

offset+fromIndex is assigned to the member variable offset of the SubList object, which is used to record the offset of the element;

toIndex-the member variable size assigned by fromIndex to the SubList object, used to record the amount of data that will be returned at this time;

The last one is the member variable modCount assigned to the SubList object by ArrayList.this.modCount. This assignment is more critical. It records the number of modifications, and the default is 0;

At this point, the construction of a SubList object is complete. You may have questions, but just simply construct a SubList object, then how to perform the value assignment; to solve this problem, take a look at the get() method of the SubList object:

In the get() method, the final return is ArrayList.this.elementData(offset + index); as you can see, it takes the value from an elementData() method maintained in the current ArrayList object, and then look at elementData() this way:

What is returned are the elements in the elementData array:

It can be seen that the set of operations in the SubList object is the same set as the set of operations in the original list, and the position of the element is marked by the offset offset plus the index; therefore, when you manipulate the original list or intercept the element, the generated list1 Collections are all affecting the same collection.

3. The climax part:

Anomaly analysis:

With the analysis of the second step above, there is a basic understanding that the collection returned by the list.subList() method will directly affect the original list collection, and then continue to analyze the cause of the java.util.ConcurrentModificationException;

Go back to the following four sentences of the test code again:

List<Integer> list1 = list.subList(0,3000);
List<Integer> list2 = list.subList(3000,6000);
list2.clear();
System.out.println("list1 = " + list1);
首先通过  List<Integer> list1 = list.subList(0,3000); 等到一个list1; 

Then pass List<Integer> list2 = list.subList(3000,6000); again and wait until a list2; 

Then clear list2 that is list2.clear();

Finally print: System.out.println("list1 = "+ list1);

As we know from the above analysis, list2 calls the clear() method, then the underlying elementData array maintained by the original list is bound to be affected at this time. Specifically, the following 3000 elements will be deleted. At this time, list1 will print again, and it will Call the iteration method iterator() that you overwrite to traverse, and then call the listIterator() method of the parent AbstractList. Since the SubList class inherits AbstractList, it will call the listIterator(final int index) method of the SubList class. At this time, this method Internally, the checkForComodification(); method is called in the first sentence:

Next, let's see what the checkForComodification() method is doing:

The point is here. This method first determines whether ArrayList.this.modCount is the same as this.modCount (that is, the modCount of SubList). If they are not the same, an exception java.util.ConcurrentModificationException will be thrown. A big circle finally wrote about this exception. When generating list1, it assigned the modCount of the original list to the SubList object when it instantiated a SubList object. At this time, the default value is 0. When list2.clear(), the original The modCount of the list has changed, that is, it is no longer 0, so when printing list1, ArrayList.this.modCount != this.modCount in the checkForComodification() method is judged to be true, so this is the reason for the exception thrown .

4. Attach a picture of the conclusion drawn from the comment on the subList() method for your reference and study:

Guess you like

Origin blog.csdn.net/feng8403000/article/details/114797903