In which cases will ArrayList report java.util.ConcurrentModificationException?

ArrayList is a very commonly used collection class, the bottom layer is an array, but ArrayList encapsulates the array, implements some useful methods such as add()methods, size()methods, get()methods and a series of methods, and implements dynamic expansion of the array.
new ArrayList();An empty array is created, and its capacity starts at 0. Why do many people say that the initial capacity of ArrayList is during the interview 10? The reason is that when you call the add()method to add an element for the first time, there will be an expansion. At this time, the capacity will be expanded to the default initial capacity 10
. A constant is defined in ArrayList DEFAULT_CAPACITY, as follows: For
Insert picture description here
the interpretation of the source code of ArrayList, please refer to the blog I wrote before:
ArrayList, LinkedList, HashMap, HashSet -----Source code interpretation ArrayList

This article mainly explains the occurrence of concurrent modification exceptions, that is, calling multi-threaded concurrent calling add()methods

Below is a piece of test code

ArrayList<String> arrayList = new ArrayList();
for (int i = 0; i < 2000; i++) {
    
    //如果没有出现并发修改异常则讲循环调大点
    int finalI = i;
    new Thread(() -> {
    
    
        arrayList.add("" + finalI);
    }, "thread:" + i).start();

}

arrayList.stream().forEach(System.out::println);//遍历ArrayList进行打印

The following is the output of the executed code
Insert picture description here

Do you have any doubts, why is there a concurrent modification exception? How was this exception thrown?

Solve doubts

Let's follow the source code with me to find the location of this exception.

I did this, add()adding a breakpoint to the method for debugdebugging.

Will execute the following code

public boolean add(E e) {
    
    
    modCount++; //注意这个参数,是来自父类的成员变量 AbstractList
    add(e, elementData, size);//近一步调用三个参数的add方法
    return true;//表示添加成功
}
private void add(E e, Object[] elementData, int s) {
    
    
    if (s == elementData.length) //判断是否需要扩容
        elementData = grow();//进行扩容,将扩容后的新数组重新赋值给 elementData
    elementData[s] = e; //将元素添加进数组中
    size = s + 1; // s是下标,size是实际的元素个数,因此需要 数组下标 +1 = 元素个数
}

You will find that the add method does not seem to have java.util.ConcurrentModificationExceptionany information about the exception.
Simply add the element, and grow()expand if the length is not enough .
Could it be grow()the exception thrown in this method? Further track togrow()

private Object[] grow() {
    
    
    return grow(size + 1); //调用了带参数的 grow方法
}

You will find that there is no code about concurrent modification exceptions.

private Object[] grow(int minCapacity) {
    
    
    int oldCapacity = elementData.length;
    if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
    
    
        int newCapacity = ArraysSupport.newLength(oldCapacity,
                minCapacity - oldCapacity, /* minimum growth */
                oldCapacity >> 1           /* preferred growth */);
        return elementData = Arrays.copyOf(elementData, newCapacity);
    } else {
    
    
        return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)];
    }
}

Call the Arrays copyOf()method to copy the elements, get a new array and return.
There is also no concurrent modification exception code. So where exactly is the exception thrown?

public static <T> T[] copyOf(T[] original, int newLength) {
    
    
    return (T[]) copyOf(original, newLength, original.getClass());
}

The exception is not add()the exception thrown in the method, but the exception thrown by the traversal printing later.
Enter the traversal. Of course, the test code uses the java8 stream operation to print.
There is a member variable left before. modCount++This member variable is self-incremented. Where does this variable come from? Trace it. When it comes to the parent class AbstarctList, you can also know from the name that this member variable is used to record the modification count.
Searching for this category ConcurrentModificationExceptionfound that there are 11 places cited (including the comments). Look at it one by one, and found the first place to be referenced in the inner class Itr. This class has been analyzed before and is used to iterate through the collection. In other words, it detects modCountthat the value is different from the expected value during iterative traversal , and then throws this exception.
The reason for this result is a multi-thread calling add()this modCountwill be incremented.
Then the thread has not finished processing, and then iterate is called to get the element, and it is found that the modCountcount is expectedModCountnot equal to the expected , so an exception is thrown

The following are Itrthe methods defined in the class and the conditions for throwing exceptions
final void checkForComodification() {
    
    
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

ItrThis method is called in two places in this inner class, namely
remove()
②This next()
means that these two methods may reportjava.util.ConcurrentModificationException

In addition to Itrthis class, there ConcurrentModificationExceptionare ListItralso calls to internal classes , respectively
previous()
set(E e)
The following detection methods are defined in the add(E e)
internal classRandomAccessSpliterator

static void checkAbstractListModCount(AbstractList<?> alist, int expectedModCount) {
    
    
    if (alist != null && alist.modCount != expectedModCount) {
    
    
        throw new ConcurrentModificationException();
    }
}

The methods called are
tryAdvance(Consumer<? super E> action)
forEachRemaining(Consumer<? super E> action)
and the
<E> E get(List<E> list, int i)method that directly throws exceptions. The
last place that may throw exceptions is in the static inner class SubList<E>, which
defines the method of detecting concurrent modification exceptions.

private void checkForComodification() {
    
    
    if (root.modCount != this.modCount)
        throw new ConcurrentModificationException();
}

In
E set(int index, E element)
E get(int index)
int size()
void add(int index, E element)
E remove(int index)
oid removeRange(int fromIndex, int toIndex)
addAll(int index, Collection<? extends E> c)
ListIterator<E> listIterator(int index)
all calls above, checkForComodification()concurrent modification exceptions may be thrown.
In other words, if these methods are called, an java.util.ConcurrentModificationExceptionexception may be thrown

The reason why the java.util.ConcurrentModificationExceptionexception is thrown in the test code is that the iterative acquisition of the element is called, and the ArrayList中的forEach方法
incoming is a consumer interface

 @Override
 public void forEach(Consumer<? super E> action) {
    
    
     Objects.requireNonNull(action);
     final int expectedModCount = modCount;
     final Object[] es = elementData;
     final int size = this.size;
     for (int i = 0; modCount == expectedModCount && i < size; i++)
         action.accept(elementAt(es, i));
     if (modCount != expectedModCount)
         throw new ConcurrentModificationException();
 }

Because modCount != expectedModCountthe condition is met in the later if judgment condition, a concurrent modification exception
is thrown. This is forEachthe reason for the concurrent modification exception caused by the flow in the test case .

to sum up:

The add(E e)methods in ArrayList will cause concurrency problems, but will not report java.util.ConcurrentModificationExceptionexceptions, but when traversing. For example forEach, throw exceptions in the test code . Concurrency exceptions are not bound to occur, but ArrayList does have concurrency problems.

In other words, no matter how many threads you use, no add(E e)exception will be thrown. Although it is true that there are repeated writes to the same array position, it is not known which thread caused the concurrent exception.

Guess you like

Origin blog.csdn.net/qq_41813208/article/details/107768960