First, local environment

  • Editor: IntelliJ IDEA 2017

  • JDK version: JDK 1.8

Second, the phenomenon caused

In some businesses, we will iterate through the collection and remove the elements that need to be removed, and may remove multiple elements. For example, when managing a group and kicking people, the business logic of kicking people is probably to go through the relationships of the group, find the person to kick and remove him.

List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
list.add("d");

for (String s : list) {
    if ("b".equals(s)) { list.remove(s); }}Copy the code

So a piece of code like this, it throws an exception

java.util.ConcurrentModificationException
	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
	at java.util.ArrayList$Itr.next(ArrayList.java:851)
	at com.lonelycountry.test3.Test1.test3(Test1.java:68)
Copy the code

But some of you are lucky enough to have written the following code, which cleverly or accidentally avoids this trap

List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
list.add("d");

for (String s : list) {
    if ("b".equals(s)) {
        list.remove(s);
        break; }}Copy the code

Iii. Solutions

1. Add a break to the code that deletes the internal elements of the collection only once

List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
list.add("d");

for (String s : list) {
    if ("b".equals(s)) {
        list.remove(s);
        break; }}Copy the code

2. Use iterators

List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
list.add("d");

Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
    String s = iterator.next();
    if ("b".equals(s)) { iterator.remove(); }}Copy the code

Four, view the source code to find problems

1, traversal method source (ArrayList as an example)

Use a for loop iterates through the collection and use iterators iterate through the set to use the same method, in the ArrayList class have an internal class implements the Iterator interface, there is a call next () method is traversal methods (cutting only a part of the way).

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess.Cloneable.java.io.Serializable {
    private class Itr implements Iterator<E> {
        public E next(a) {
            checkForComodification();// This is where the exception is thrown
            // The bottom is normal cursor movement
            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]; }}}Copy the code

The first line of the next() method calls the checkForComodification() method, which is used to verify that there are no illegal operations.

final void checkForComodification(a) {
    ModCount++ = modCount++ = modCount++
    // When a method is called, the predicted operands and the actual operands are not synchronized
    if(modCount ! = expectedModCount)throw new ConcurrentModificationException();
}
Copy the code

2, ArrayList itself remove(Object O) method source analysis

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess.Cloneable.java.io.Serializable {
    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); // Focus on this method
                    return true; }}return false;
    }
    
    private void fastRemove(int index) {
        modCount++; // this is what causes !!!!
        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}}Copy the code

In the remove(Object O) method, the index to be deleted is found. After the index is valid, the fastRemove(int Index) method is executed. The first step of the method is to +1 the actual operand and then perform the array operation, while the expectedModCount does not change. It is expected that when the next() method is executed, the checkForComodification() method will definitely throw an exception.

public static native void arraycopy(Object src,  int  srcPos, Object dest, int destPos, 
    int length);
Copy the code

The arrayCopy () method of the System class is used for group operations. The underlying call should be C or C++ (I guess). I checked the documentation and explained the logic.

Index srcPos, length srcPos, and then overwrite the destPos position in dest. But why in the operation group with such a complex method I do not know, there is a master of the algorithm SAO years can guide me oh.

3, ArrayList iterator inner class remove() method source analysis

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess.Cloneable.java.io.Serializable {
    private class Itr implements Iterator<E> {
        public void remove(a) {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw newConcurrentModificationException(); }}}}Copy the code

This method essentially calls the remove(int index) method of the ArrayList itself, but secretly synchronise the actual operands with the predicted operands because this line of logic causes the iterator to iterate over the delete without throwing an exception. You must be wondering why you don’t add a line to the Remove (Object O) method if you know why. This is not possible because the variable expectedModCount is a variable in the Itr inner class and the Remove (Object O) method is declared outside the ArrayList class. Cannot manipulate variables that interfere with inner classes.

Five, the summary

This paper mainly analyzes the code differences between the two traversal sets, and the locations and causes of errors. There is also the puzzle of why the system.arrayCopy () method is used when calling the fastRemove(int index) method.