[Java] In-depth understanding of concurrent modification exceptions and lightning protection methods

chatting part

Before we get to the point, let's talk about several grammatical traversal methods of the list collection in the java language

The first type: traditional for loop

for (int i = 0; i < list.size(); i++) {
    
    
    System.out.println(list.get(i));
}

The second: enhanced for loop

for (String value : list) {
    
    
    System.out.println(value);
}

The third type: forEach

list.forEach(System.out::println);

The fourth type: stream flow

list.stream().forEach(System.out::println);

There is another kind, iterator

Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
    
    
    System.out.println(iterator.next());
}

In fact, the third and fourth types also use the iterator iterator to process when traversing the collection. However, if the iterator method is used improperly, it may cause concurrent modification exceptions. So today Xiaobai will talk about it Talk about how to avoid this problem in daily development

Concurrent modification is abnormally long like this, don’t say you don’t know it when you see it in the future

ConcurrentModificationException
image-20230508102400637

Closer to home

Crime scene: see source code

List<String> list = new ArrayList<>();
list.add("11");
list.add("12");
list.add("13");
list.add("14");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
    
    
    String value = iterator.next();
    if ("12".equals(value)) {
    
    
        list.remove(value);
    }
}

In the above code, what we call the concurrent modification exception occurs.

image-20230508102825632

As shown in the figure, we can see from the console that the culprit is in line 22 of the program, so why is this exception thrown? let's explore

First, we find the Itr class, which is an anonymous class in ArrayList and implements the Iterator interface. List.iterator() returns an instance of this class

image-20230508103135272

Please look at the picture above, the class is very simple, the constructor does not do anything, three member variables

cursor: returns the index of the next element, int initialized to 0

lastRet: Returns the index of the last element, or -1 if there is no

Look at line 849, modCount is a member variable in ArrayList, indicating the number of modifications to the collection, and assign it to the expectedModCount variable

So look at the key point, in our traversal code, there seem to be only two methods, let's post them separately

hasNext(): size is the number of elements in the collection, cursor is initially 0

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

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

Looking at the above method, there is a checkForComodification() at the front of the element acquisition, so what does this do?

final void checkForComodification() {
    
    
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

Finally, I saw ConcurrentModificationException, which was originally thrown here, which is consistent with the exception thrown in line 22 above, just in the method of next()

It will judge whether the number of modifications of the collection is consistent with expectedModCount (expected number of modifications), and if not, an exception will be thrown

The expectedModCount variable is an instance variable of the generator, if there is an inconsistency, could it be that modCount has changed?

Can we understand that in the iterative process, modCount cannot be changed, or is expectedModCount consistent with modCount?

Does the above use the list.remove() method modify modCount? Yes

image-20230508104459340

Look at the picture above, remove once, modCount will be ++, then the next time next(), expectedModCount and modCount are inconsistent

Not only remove, but also add, please do your own research

So the problem is found, how to solve it, according to our above analysis, there are three solutions?

1. modCount cannot be changed

This is simple, in the process of traversal, do not operate on the elements in the collection

2. After modCount++, the next next() is not allowed

Case code transformation: manual break

List<String> list = new ArrayList<>();
list.add("11");
list.add("12");
list.add("13");
list.add("14");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
    
    
    String value = iterator.next();
    if ("12".equals(value)) {
    
    
        list.remove(value);
        break;
    }
}
System.out.println(list);

3. Always make expectedModCount consistent with modCount

We have operated on the elements in the collection, modCount will definitely change, and the variable expectedModCount has no exposure method and cannot be modified directly. In fact, Iterator also has a remove method

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

Not much else to say, look at the fifth last line, the problem is solved, after deleting the element, manually let expectedModCount = modCount

Then the source code is changed to

List<String> list = new ArrayList<>();
list.add("11");
list.add("12");
list.add("13");
list.add("14");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
    
    
    String value = iterator.next();
    if ("12".equals(value)) {
    
    
        iterator.remove();
    }
}
System.out.println(list);

epilogue

1. There are many ways to traverse the collection. When using the iterator to traverse, you cannot use the remove provided by the list to delete elements. Iterator.next() will first check whether the expectedModCount and modCount are equal. If they are not equal, a concurrent modification exception will be thrown.

2. Use the remove method provided by Iterator to delete elements, and the bottom layer will manually make expectedModCount equal to modCount.

3. ForEach and stream.forEach() will also be translated into iterators when traversing the collection, and deleting elements will also throw concurrent modification exceptions.

4. It's not easy to make, let's go with one click and three consecutive times. Your support will always be my biggest motivation!

Guess you like

Origin blog.csdn.net/admin_2022/article/details/130555576