Java ~ El mecanismo a prueba de fallas en la colección

Introducción

Sabemos que muchas colecciones bajo la interfaz Collection en Java no son seguras para subprocesos. Por ejemplo, java.util.ArrayList no es seguro para subprocesos, por lo que si otros subprocesos modifican la lista mientras usan el iterador, se lanzará ConcurrentModificationException. Esto es la llamada estrategia de falla rápida.

La implementación de esta estrategia en el código fuente es a través del campo modCount. ModCount es el número de modificaciones como sugiere el nombre. Las modificaciones al contenido de ArrayList incrementarán este valor. Luego este valor se asignará al valor esperadoModCount del iterador durante el proceso de inicialización del iterador. En el proceso iterativo, juzgue si modCount ypectedModCount son iguales. Si no son iguales, significa que otros subprocesos han modificado la lista.
Tenga en cuenta que modCount se declara como volátil para garantizar la visibilidad de los cambios entre subprocesos.

modCount 和 esperadoModCount

modCount ypectedModCount se utilizan para representar el número de modificaciones, donde modCount representa el número de modificaciones a la colección, que incluye las modificaciones realizadas cuando se llaman los métodos add, remove y clear de la colección en sí, y los métodos de modificación de la colección. El iterador de colección se llama modificar. El valor esperadoModCount es el número de veces que el iterador modifica la colección.

El propósito de establecer esperabaModCount es garantizar que durante el uso del iterador, el objeto de lista solo pueda tener este iterador para modificar la lista.

Al crear el iterador, el valor del modCount del objeto se pasa al esperadoModCount del iterador:

  private class ListItr implements ListIterator<E> {
    
    
         private Node<E> lastReturned;
         private Node<E> next;
         private int nextIndex;
         private int expectedModCount = modCount;

Si crea varios iteradores para modificar un objeto de colección, entonces habrá un modCount y variospectedModCount, y el valor de modCount será diferente, lo que conduce a la inconsistencia de los valores de moCount y esperabaModCount, lo que da como resultado una excepción:

public E next() {
    
    
            checkForComodification();
            if (!hasNext())
                throw new NoSuchElementException();

            lastReturned = next;
            next = next.next;
            nextIndex++;
            return lastReturned.item;
        }

El checkForComodification en el código anterior verificará si los valores de modCount ypectedModCount son los mismos, y lanzará una excepción si no coinciden.

   final void checkForComodification() {
    
    
             if (modCount != expectedModCount)
                 throw new ConcurrentModificationException();
         

Cómo se modifica modCount

    // 添加元素到队列最后
    public boolean add(E e) {
    
    
        // 修改modCount
        ensureCapacity(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }


    // 添加元素到指定的位置
    public void add(int index, E element) {
    
    
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(
            "Index: "+index+", Size: "+size);

        // 修改modCount
        ensureCapacity(size+1);  // Increments modCount!!
        System.arraycopy(elementData, index, elementData, index + 1,
             size - index);
        elementData[index] = element;
        size++;
    }

    // 添加集合
    public boolean addAll(Collection<? extends E> c) {
    
    
        Object[] a = c.toArray();
        int numNew = a.length;
        // 修改modCount
        ensureCapacity(size + numNew);  // Increments modCount
        System.arraycopy(a, 0, elementData, size, numNew);
        size += numNew;
        return numNew != 0;
    }


    // 删除指定位置的元素
    public E remove(int index) {
    
    
        RangeCheck(index);

        // 修改modCount
        modCount++;
        E oldValue = (E) elementData[index];

        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index, numMoved);
        elementData[--size] = null; // Let gc do its work

        return oldValue;
    }


    // 快速删除指定位置的元素
    private void fastRemove(int index) {
    
    

        // 修改modCount
        modCount++;
        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // Let gc do its work
    }

    // 清空集合
    public void clear() {
    
    
        // 修改modCount
        modCount++;

        // Let gc do its work
        for (int i = 0; i < size; i++)
            elementData[i] = null;

        size = 0;
    }
  • Es decir, modcount ++ se ejecutará cuando se agreguen o eliminen datos a la colección, por lo que si un hilo todavía está usando el iterador para recorrer la lista, se encontrará una excepción y se producirá un fallo rápido.

Comparación de fallos rápidos y a prueba de fallos

El rápido fallo de Iterator se basa en el hecho de que la copia de la colección subyacente es una copia superficial, por lo que se ve afectada por la modificación en la colección de origen. Todas las clases de colección del paquete java.util fallan rápidamente

Todas las clases del paquete java.util.concurrent usan bloqueos para lograr fallas de seguridad.

Los iteradores que fallan rápidamente lanzarán ConcurrentModificationException, mientras que los iteradores que fallan de forma segura nunca lanzarán tal excepción.

¿Qué problema resuelve la falla rápida?

El mecanismo a prueba de fallos es un mecanismo de detección de errores.
Solo se puede usar para detectar errores, porque el JDK no garantiza que sucederá el mecanismo de falla rápida. Simplemente le dice al cliente que se ha producido un problema de seguridad de subprocesos múltiples en un entorno de subprocesos múltiples.
Por lo tanto, si el conjunto de mecanismos a prueba de fallas se usa en un entorno multiproceso, se recomienda usar "clases en el paquete java.util.concurrent" para reemplazar "clases en el paquete java.util".

Cómo resolver eventos de fallas rápidas

Se explica el CopyOnWriteArrayList correspondiente a ArrayList. Echemos un vistazo al código fuente de CopyOnWriteArrayList:

public class CopyOnWriteArrayList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    
    

    ...

    // 返回集合对应的迭代器
    public Iterator<E> iterator() {
    
    
        return new COWIterator<E>(getArray(), 0);
    }

    ...

    private static class COWIterator<E> implements ListIterator<E> {
    
    
        private final Object[] snapshot;

        private int cursor;

        private COWIterator(Object[] elements, int initialCursor) {
    
    
            cursor = initialCursor;
            // 新建COWIterator时,将集合中的元素保存到一个新的拷贝数组中。
            // 这样,当原始集合的数据改变,拷贝数据中的值也不会变化。
            snapshot = elements;
        }

        public boolean hasNext() {
    
    
            return cursor < snapshot.length;
        }
  • CopyOnWriteArrayList implementa Iterator por sí mismo, y no existe el llamado checkForComodification () en la clase de implementación Iterator de CopyOnWriteArrayList, y no arrojará ConcurrentModificationException.
  • CopyOnWriteArrayList guarda los elementos de la colección en una nueva matriz de copia al crear un nuevo COWIterator. De esta forma, cuando cambian los datos del conjunto original, el valor de los datos copiados no cambiará.

Supongo que te gusta

Origin blog.csdn.net/Shangxingya/article/details/113779220
Recomendado
Clasificación