CopyOnWriteArrayList análisis de código fuente central

Título de la imagen

Seguí corriendo solo para ponerme al día conmigo mismo que tenía grandes esperanzas. -Livingston

0 Prefacio

Sabemos que ArrayList no seguro para subprocesos, se necesita poseer o utilizar un bloqueo Collections.synchronizedListde embalaje contenedor proporciona mecanismos compatibles con el proceso de concurrencia para lograr el uso de copy-on-write JDK1.5 inicio JUC en la lista -. CopyOnWriteArrayList, denominado VACA

Título de la imagen

1 ideas de diseño CopyOnWrite

1.1 Conceptos básicos

CopyOnWrite copy-on-write. En términos generales, cuando agregamos elementos a un contenedor, no agregamos directamente al contenedor actual, sino que primero copiamos el contenedor actual de un nuevo contenedor, agregamos elementos al nuevo contenedor, agregamos elementos Luego, apunte el contenedor original al nuevo contenedor. Es decir, todos están compartiendo el mismo contenido al principio. Cuando alguien quiere modificar el contenido, en realidad lo copiará para formar uno nuevo y luego lo cambiará. Esto es una especie de Retrasa la estrategia perezosa.

1.2 Ventajas de diseño

El contenedor CopyOnWrite puede leerse simultáneamente sin bloqueo, ya que el contenedor actual no agregará ningún elemento, por lo que esta también es una idea separada de lectura y escritura, leer y escribir diferentes contenedores.

2 sistema de herencia

  • Similar al sistema de herencia de ArrayList
    Título de la imagen
    Título de la imagen

3 propiedades

  • Cerraduras para proteger todos los modificadores
    Título de la imagen
  • Arreglos a los que solo se puede acceder a través de getArray / setArray
    Título de la imagen
  • bloqueo de memoria offset
    Título de la imagen

4 método de construcción

4.1 Sin parámetros

  • Crea una lista vacía
    Título de la imagen

4.2 Participación

  • Cree una lista que contenga los elementos de la colección especificada, cuyo orden es devuelto por el iterador de la colección.
    Título de la imagen
  • Crear una lista que contenga una copia de la matriz dada
    Título de la imagen

Comencemos mirando el código fuente y cómo copiarlo en escritura.

5 agregar (E y)

Agregar elementos a la VACA requiere bloqueo, de lo contrario, se copiarán N copias durante la escritura concurrente.

public boolean add(E e) {
    final ReentrantLock lock = this.lock;
    // 1.加锁
    lock.lock();
    try {
        // 得到原数组
        Object[] elements = getArray();
        int len = elements.length;
        // 2.复制出新数组,加一是因为要添加yi'ge'yuan's
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        // 把新元素添加到新数组里,直接放在数组尾部
        newElements[len] = e;
        // 把原数组引用指向新数组
        setArray(newElements);
        return true;
    } finally {
        // finally 里面释放锁,保证即使 try 发生了异常,仍然能够释放锁 
        lock.unlock();
    }
}
复制代码

getArray

  • Obtenga una matriz. No privada, para que también se pueda acceder a ella desde la clase CopyOnWriteArraySet (que combina directamente CopyOnWriteArrayList como una variable miembro)
    Título de la imagen

setArray

  • Establecer referencia a una nueva matriz
    Título de la imagen

Ambos están bloqueados, ¿por qué necesita copiar la matriz sin modificar directamente la matriz original?

  • volátil se modifica mediante una referencia de matriz. Simplemente modifique el valor de algunos elementos en la matriz original. Esta operación no se puede usar para la visibilidad. Debe modificar la dirección de memoria de la matriz
  • La ejecución de copyOf en la nueva matriz no tiene efecto en la matriz original. Solo después de que la nueva matriz se haya copiado por completo, se puede acceder externamente, evitando los posibles efectos adversos de los cambios de datos en la matriz original.

6 obtener

get (int index)

  • Leer el elemento de posición especificado
    Título de la imagen

get (Objeto [] a, int index)

Título de la imagen

No es necesario agregar un bloqueo durante la lectura. Si otros subprocesos agregan datos a la ArrayList durante la lectura, la lectura solo leerá los datos antiguos, porque la matriz anterior no se bloqueará al escribir.

7 eliminar

7.1 Eliminar índice especificado

public E remove(int index) {
    final ReentrantLock lock = this.lock;
    // 加锁
    lock.lock();
    try {
        Object[] elements = getArray();
        int len = elements.length;
        // 先得到旧值
        E oldValue = get(elements, index);
        int numMoved = len - index - 1;
        // 如果要删除的数据正好是数组的尾部,直接删除
        if (numMoved == 0)
            setArray(Arrays.copyOf(elements, len - 1));
        else {
            // 若删除的数据在数组中间:
            // 1. 设置新数组的长度减一,因为是减少一个元素
            // 2. 从 0 拷贝到数组新位置
            // 3. 从新位置拷贝到数组尾部
            Object[] newElements = new Object[len - 1];
            System.arraycopy(elements, 0, newElements, 0, index);
            System.arraycopy(elements, index + 1, newElements, index,
                             numMoved);
            setArray(newElements);
        }
        return oldValue;
    } finally {
        lock.unlock();
    }
}
复制代码

Todavía tres ejes:

  1. Cerradura
  2. Dependiendo de dónde se elimine el índice, haga diferentes copias de políticas
  3. Desbloquear

7.2 Eliminación masiva

public boolean removeAll(Collection<?> c) {
    if (c == null) throw new NullPointerException();
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        Object[] elements = getArray();
        int len = elements.length;
        if (len != 0) {
            // newlen 表新数组的索引位置,新数组中存在不包含在 c 中的元素
            int newlen = 0;
            Object[] temp = new Object[len];
            // 循环,把不包含在 c 里面的元素,放到新数组中
            for (int i = 0; i < len; ++i) {
                Object element = elements[i];
                // 不包含在 c 中的元素,从 0 开始放到新数组中
                if (!c.contains(element))
                    temp[newlen++] = element;
            }
            // 拷贝新数组,变相的删除了不包含在 c 中的元素
            if (newlen != len) {
                setArray(Arrays.copyOf(temp, newlen));
                return true;
            }
        }
        return false;
    } finally {
        lock.unlock();
    }
}
复制代码

En lugar de eliminar directamente los elementos de la matriz uno por uno, primero juzgue los valores de la matriz en un bucle, coloque los datos que no necesitan ser eliminados en la matriz temporal, y finalmente los datos en la matriz temporal son los datos que no necesitamos eliminar.

8 Resumen

El contenedor de concurrencia CopyOnWrite es adecuado para escenarios concurrentes donde leer más y escribir menos El contenedor CopyOnWrite tiene muchas ventajas, pero también hay problemas.

Problemas de uso de memoria

Al escribir, la memoria de dos objetos residirá en la memoria al mismo tiempo, el objeto antiguo y el objeto recién escrito (solo la referencia en el contenedor se copia al copiar, pero el nuevo objeto se crea y se agrega al nuevo contenedor al escribir, y Los objetos del contenedor todavía están en uso, por lo que hay dos copias de la memoria del objeto. Si estos objetos ocupan una gran cantidad de memoria, es probable que provoque GC frecuente y el tiempo de respuesta de la aplicación se alargue. Memoria, o no use directamente el contenedor CopyOnWrite, pero use otros contenedores concurrentes, como ConcurrentHashMap.

Problemas de consistencia de datos

El contenedor CopyOnWrite solo puede garantizar datos, 最终一致性pero no datos 实时一致性, utilícelo según corresponda.

Supongo que te gusta

Origin juejin.im/post/5e93281d51882573716a9c8b
Recomendado
Clasificación