¿En qué casos ArrayList informará de java.util.ConcurrentModificationException?

ArrayList es una clase de colección de uso muy común, la capa inferior es una matriz, pero ArrayList encapsula la matriz, implementa algunos métodos útiles como add()métodos, size()métodos, get()métodos y una serie de métodos, e implementa la expansión dinámica de la matriz.
new ArrayList();Se crea una matriz vacía y su capacidad comienza en 0. ¿Por qué mucha gente dice que la capacidad inicial de ArrayList es durante la entrevista 10? La razón es que cuando llame al add()método para agregar un elemento por primera vez, habrá una expansión. En este momento, la capacidad se expandirá a la capacidad inicial predeterminada 10
. Se define una constante en ArrayList DEFAULT_CAPACITY, de la siguiente manera: Para
Inserte la descripción de la imagen aquí
la interpretación del código fuente de ArrayList, consulte el blog que escribí antes:
ArrayList, LinkedList, HashMap, HashSet ----- Interpretación del código fuente Lista de arreglo

Este artículo explica principalmente la ocurrencia de excepciones de modificación concurrente, es decir, llamar a add()métodos de llamada concurrente de subprocesos múltiples

A continuación se muestra un fragmento de código de prueba

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进行打印

La siguiente es la salida del código ejecutado
Inserte la descripción de la imagen aquí

¿Tiene alguna duda, por qué hay una excepción de modificación concurrente? ¿Cómo se lanzó esta excepción?

Resuelve dudas

Sigamos el código fuente conmigo para encontrar la ubicación de esta excepción.

Hice esto, add()agregando un punto de interrupción al método de debugdepuración.

Ejecutará el siguiente código

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 = 元素个数
}

Verá que el método add no parece tener java.util.ConcurrentModificationExceptioninformación sobre la excepción.
Simplemente agregue el elemento y grow()expanda si la longitud no es suficiente .
¿Podría ser grow()la excepción lanzada en este método? Más pista agrow()

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

Descubrirá que no hay ningún código sobre excepciones de modificación simultánea.

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

Llame al copyOf()método Arrays para copiar los elementos, obtener una nueva matriz y regresar.Tampoco
hay código de excepción de modificación concurrente. Entonces, ¿dónde se lanza exactamente la excepción?

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

La excepción no es add()la excepción lanzada en el método, sino la excepción lanzada por la impresión transversal más tarde.
Ingrese el recorrido transversal. Por supuesto, el código de prueba usa la operación de flujo java8 para imprimir.
Hay una variable miembro antes. modCount++Esta variable miembro se incrementa automáticamente. ¿De dónde viene esta variable? Rastree. Cuando se trata de la clase principal AbstarctList, también puede saber por el nombre que esta variable miembro se usa para registrar el recuento de modificaciones.
Al buscar esta categoría, se ConcurrentModificationExceptionencontró que hay 11 lugares citados (incluidos los comentarios). Mírelo uno por uno, y encontró el primer lugar al que se hace referencia en la clase interna Itr. Esta clase ha sido analizada antes y se usa para iterar a través de la colección. En otras palabras, detecta modCountque el valor es diferente del valor esperado durante el recorrido iterativo y luego lanza esta excepción.
La razón de este resultado es un multi-hilo llamando add()esta modCountserá incrementado.
Entonces el hilo no ha terminado de procesarse, y luego se llama a iterar para obtener el elemento, y se encuentra que el modCountrecuento expectedModCountno es igual al esperado , por lo que se lanza una excepción

Los siguientes son Itrlos métodos definidos en la clase y las condiciones para lanzar excepciones
final void checkForComodification() {
    
    
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

ItrEste método se llama en dos lugares en esta clase interna, a saber
remove()
② Esto next()
significa que estos dos métodos pueden informarjava.util.ConcurrentModificationException

Además de Itresta clase ConcurrentModificationException, las clases internas ListItrtambién tienen llamadas respectivamente
previous()
set(E e)
Los siguientes métodos de detección se definen en la add(E e)
clase internaRandomAccessSpliterator

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

Los métodos llamados son
tryAdvance(Consumer<? super E> action)
forEachRemaining(Consumer<? super E> action)
y el método
que arroja excepciones directamente. El último lugar que puede lanzar excepciones es en la clase interna estática , que define el método de detección de excepciones de modificación concurrente.<E> E get(List<E> list, int i)
SubList<E>

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

En
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)
todas las llamadas anteriores, checkForComodification()se pueden lanzar excepciones de modificación concurrentes.
En otras palabras, si se llaman a estos métodos, se puede lanzar una java.util.ConcurrentModificationExceptionexcepción

La razón por la que java.util.ConcurrentModificationExceptionse lanza la excepción en el código de prueba es que se llama a la adquisición iterativa del elemento y la ArrayList中的forEach方法
entrada es una interfaz de consumidor

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

Debido a que modCount != expectedModCountla condición se cumple en la condición de juicio if posterior,
se lanza una excepción de modificación concurrente. Esta es forEachla razón de la excepción de modificación concurrente causada por el flujo en el caso de prueba .

para resumir:

Los add(E e)métodos en ArrayList causarán problemas de simultaneidad, pero no reportarán java.util.ConcurrentModificationExceptionexcepciones, pero al atravesar. Por ejemplo forEach, lanza excepciones en el código de prueba . Las excepciones de concurrencia no están destinadas a ocurrir, pero ArrayList tiene problemas de concurrencia.

En otras palabras, no importa cuántos subprocesos use, no se add(E e)lanzará ninguna excepción.Aunque de hecho hay escrituras repetidas en la misma posición de matriz, no se sabe qué subproceso causó la excepción concurrente.

Supongo que te gusta

Origin blog.csdn.net/qq_41813208/article/details/107768960
Recomendado
Clasificación