serie JDK fuente (4): CopyOnWriteArrayList

CopyOnWriteArrayList

1. Introducción

CopyOnWriteArrayListpaquete Juc es un contenedores concurrentes a modo de hilo de seguridad, el subyacente implementarse usando arrays. Copy-on-write su nombre indica es un medio de copia en escritura, la idea básica es que desde el principio que todos compartimos el mismo contenido, cuando alguien quiere modificar el contenido, el contenido de copia saldrá a formar un nuevo contenido y luego hacer cambios este es un perezoso tácticas de demora.

Después de asumir que los elementos de añadir a un recipiente cuando no se añade directamente del recipiente a la corriente, pero el bloqueo de contenedores corriente conducida Copia de la primera copia de un nuevo contenedor, y luego añadir un nuevo elemento en el contenedor, hemos añadido elementos, a continuación, el punto de referencia original a los nuevos buques portacontenedores, y finalmente liberar el bloqueo. La ventaja de esto es que podemos leer el envase copy-on-write concurrente sin la necesidad de bloquear solamente bloquear sólo cuando modificado, a fin de lograr la separación leer y escribir.

CopyOnWriteArrayList esquemas:
Aquí Insertar imagen Descripción

1.1, la interfaz núcleo / definiciones de métodos

    /**
     * 显示锁对象
     */
    final transient ReentrantLock lock = new ReentrantLock();
    /**
     * 内部底层数组,volatile 关键字修饰
     */
    private transient volatile Object[] array;
    /**
     * 返回内部 array
     */
    final Object[] getArray() {
        return array;
    }
    /**
     * 设置 array
     */
    final void setArray(Object[] a) {
        array = a;
    }

CopyOnWriteArrayListSe utiliza bloqueo de escritura ReentrantLock, la parte inferior es ser una volatilevariedad modificada clave. El constructor también relativamente simple, no se presentó, estamos interesados pueden ver por sí mismo a continuación.

1,2, constructor

1.3, ejemplos de demostración

1.4 escenarios de aplicación

2, el principio

2.1, método add

    public boolean add(E e) {
        // 加锁
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            // 把原数组中的元素拷贝到新数组中,并使数组容量 + 1
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            // 新元素添加到新数组中
            newElements[len] = e;
            // 重新赋值 array
            setArray(newElements);
            return true;
        } finally {
            // 释放锁
            lock.unlock();
        }
    }

Fuente apreciará fácilmente, cuando los elementos añadidos para crear una nueva matriz, la longitud de la matriz más la longitud del original 1, los elementos originales de la matriz se copian en la nueva matriz, los elementos pueden ser insertados en la nueva matriz.

2.2, método get

    public E get(int index) {
        return get(getArray(), index);
    }
    
    
    private E get(Object[] a, int index) {
        return (E) a[index];
    }

Nota: El método de obtención del elemento no se requiere bloqueado.

2.3, método remove

    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
                Object[] newElements = new Object[len - 1];
                // 分两次完成元素拷贝
                System.arraycopy(elements, 0, newElements, 0, index);
                System.arraycopy(elements, index + 1, newElements, index,
                                 numMoved);
                // 重置 array
                setArray(newElements);
            }
            // 返回老 value
            return oldValue;
        } finally {
            // 释放锁
            lock.unlock();
        }
    }

Guardar se crea una nueva matriz en una longitud elemento de matriz de origen eliminar la ubicación especificada, a continuación, indexmarca de índice, en dos ejemplares, y se copian en el nuevo elemento de la matriz.

2.4, método addIfAbsent

En general, el CopyOnWriteArrayListmétodo de la interior son relativamente fáciles de entender, hay dos métodos son relativamente difíciles de comprender:

  • remove(Object o): Elimina los valores de los elementos especificados (sólo eliminar la primera aparición)
  • addIfAbsent(E e): Sólo tiene que añadir el elemento en condiciones que no existen

Ambos métodos implican la instantánea ( snapshotobjeto), el siguiente código específico es:

    public boolean addIfAbsent(E e) {
        // 因为这里不加锁,因此使用快照对象
        Object[] snapshot = getArray();
        // 从头开始查找,如果该元素已存在,则返回 false,不存在才添加
        return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false :
            addIfAbsent(e, snapshot);
    }

indexof Un método para el retorno de la posición original del elemento especificado en la matriz, el retorno -1 no existe, el siguiente código:

    private static int indexOf(Object o, Object[] elements,
                               int index, int fence) {
        // null 与非 null 元素走不同的查找逻辑
        if (o == null) {
            for (int i = index; i < fence; i++)
                if (elements[i] == null)
                    return i;
        } else {
            for (int i = index; i < fence; i++)
                if (o.equals(elements[i]))
                    return i;
        }
        // 没有该元素返回 -1
        return -1;
    }

A continuación, hay un enfoque de alta energía relativamente de:

    private boolean addIfAbsent(E e, Object[] snapshot) {
        // 加锁
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            // 获取当前数组
            Object[] current = getArray();
            int len = current.length;
            /**
             * 并发环境下造成的不一致情况
             * 因为获取快照数组的时候没有加锁,别的线程可能修改了原数组,
             * 就会造成快照数组与原数组不一致
             */
            if (snapshot != current) {
                // Optimize for lost race to another addXXX operation
                // 获取较小的数组长度
                int common = Math.min(snapshot.length, len);
                // 可以分两种情况考虑,1 添加的时候删除了元素 2 一直添加元素
                for (int i = 0; i < common; i++)
                    // 如果添加的元素在原数组中存在,则结束循环
                    if (current[i] != snapshot[i] && eq(e, current[i]))
                        return false;
                    // 和上面的 for 循环正好组成一个数组大小,用于判断原数组中是否已经存在添加的元素
                if (indexOf(e, current, common, len) >= 0)
                        return false;
            }
            // 要添加的元素在原数组中不存在
            Object[] newElements = Arrays.copyOf(current, len + 1);
            newElements[len] = e;
            setArray(newElements);
            return true;
        } finally {
            // 释放锁
            lock.unlock();
        }
    }

En el caso concurrente, porque la matriz para obtener una instantánea de la matriz original puede ser incompatible (si otro hilo ha añadido elementos), y por lo tanto necesitan para determinar si el elemento añadido ya existe.

3, Resumen

Usted puede preguntar directamente bloqueo no se soluciona el problema, sin embargo, por qué no participar en un archivo de instantánea que? No se moleste, porque el uso de una serie de instantáneas en el caso del punto de acceso muy frecuente puede mejorar el rendimiento.

  • Crea un nuevo array cada vez que los elementos de una matriz a ser modificadas, por lo que no existe un mecanismo para la expansión
  • Leer elemento no está bloqueado, si está bloqueado elementos de forzado
  • Que permite el almacenamiento nullelemento
  • CopyOnWriteArraySet El principio subyacente es el uso de CopyOnWriteArrayList
  • CopyOnWriteArrayList Leer y escribir usando la cantidad de la escena, el próximo escenario si escribe más escenas más consumo de memoria

Supongo que te gusta

Origin www.cnblogs.com/muyutingfeng/p/12615011.html
Recomendado
Clasificación