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
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
¿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 debug
depuració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.ConcurrentModificationException
informació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 ConcurrentModificationException
encontró 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 modCount
que 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 modCount
será incrementado.
Entonces el hilo no ha terminado de procesarse, y luego se llama a iterar para obtener el elemento, y se encuentra que el modCount
recuento expectedModCount
no es igual al esperado , por lo que se lanza una excepción
Los siguientes son Itr
los métodos definidos en la clase y las condiciones para lanzar excepciones
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
Itr
Este 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 Itr
esta clase ConcurrentModificationException
, las clases internas ListItr
tambié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.ConcurrentModificationException
excepción
La razón por la que java.util.ConcurrentModificationException
se 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 != expectedModCount
la condición se cumple en la condición de juicio if posterior,
se lanza una excepción de modificación concurrente. Esta es forEach
la 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.ConcurrentModificationException
excepciones, 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.