1. Introducción al Fail-Fast
"Fail Fast" también se llama Fail-Fast, que es un mecanismo de detección de errores para las colecciones Java. Cuando un hilo itera sobre la colección, otros hilos no pueden modificar la colección estructuralmente.
Por ejemplo: Suponga que hay dos hilos (hilo 1 y hilo 2), y el hilo 1 atraviesa los elementos en el iterador Set A. En algún momento, el hilo 2 modifica la estructura de SET A (una modificación en la estructura, en lugar de simplemente modificar el contenido del elemento establecido), entonces el programa lanzará una excepción concurrente de EXPERECIÓN, generando así un fallido.
No se puede garantizar el comportamiento de falla rápida del iterador, no puede garantizar que el error ocurra, por lo que la concurrencia concurrente, la Excepción se debe usar solo para detectar errores.
Todas las clases de colección en el paquete java.util fallan rápidamente, mientras que las clases de colección en el paquete java.util.concurrent fallan de manera segura;
El iterador que falla rápidamente arroja una concurrencia de modificación concurrente, mientras que el iterador que falla de manera segura nunca arroja esta excepción.
2 ejemplos de fallas rápidas
Código de muestra: (FastFailTest.Java)
import java.util.*; import java.util.concurrent.*;/*** @Desc Programa de prueba para Fast-Fail en Java Collection. * * Condiciones para el evento de falla rápida: cuando múltiples subprocesos funcionan en la colección, si uno de los hilos atraviesa la colección a través del iterador, el contenido de la colección cambia por otros hilos; Se lanzará una excepción concurrente de modificación. * Solución de fallas rápidas: si la procesa a través de la clase correspondiente en el paquete de colección Util. * * En este ejemplo, los dos casos de ArrayList y CopyOnWriteArrayList se prueban respectivamente. ArrayList generará un evento de fallas rápidas, mientras que CopyOnWriteArrayList no generará un evento de fallas rápidas. * (01) Cuando se usa ArrayList, se generará un evento de fail rápido y se lanzará una excepción de Modificación de Excepción de concurrencia; La definición es la siguiente: * Lista estática privada <String> list = new ArrayList <String> (); * (02) Cuando se usa CopyOnWriteArrayList, no se generará un evento de falla rápida; La definición es la siguiente: * Lista estática privada <String> list = new CopyOnWriteArrayList <String> (); * * @author Skywang */public class FastFailTest {private static list <string> list = new ArrayList <String> (); // Lista estática privada <String> list = new CopyOnWriteArrayList <String> (); Public static void main (string [] args) {// Inicie dos hilos al mismo tiempo para operar en la lista. new ThreadOne (). Start (); new ThreadTwo (). Start (); } private static void printall () {system.out.println (""); Valor de cadena = nulo; Iterator iter = list.iterator (); while (iter.hasnext ()) {value = (string) iter.next (); System.out.print (valor+","); }} /*** Agregue 0,1,2,3,4,5 a la lista a su vez. Después de agregar un número, itera a través de imprime () */ private static class Threadone extiende hilo {public void run () {int i = 0; while (i <6) {list.add (string.ValueOf (i)); printall (); i ++; }}} /*** Agregue 10,11,12,13,14,15 a la lista a su vez. Después de agregar cada número, atravesará toda la lista a través de imprime () */ private static class threadtwo extiende hilo {public void run () {int i = 10; while (i <16) {list.add (string.ValueOf (i)); printall (); i ++; }}}} ¡Ejecute el código como resultado y lanza una excepción java.util.concurrentModificationException! Es decir, se genera un evento de fallas!
Descripción de los resultados
(01) En FastFailTest, inicie dos hilos al mismo tiempo para operar la lista a través de New ThreadOne (). Start () y New ThreadTwo (). Start ().
Hilo ThreadOne: Agregue 0, 1, 2, 3, 4, 5 a la lista. Después de agregar cada número, toda la lista se atraviesa a través de Printall ().
Threadtwo Hilo: Agregar 10, 11, 12, 13, 14, 15 a la lista. Después de agregar cada número, toda la lista se atraviesa a través de Printall ().
(02) Cuando un hilo atraviesa la lista, el contenido de la lista cambia por otro hilo; Se lanzará una excepción concurrente de Modificación Excepción, lo que resulta en un evento de fallas.
3. Solución de falla-rápida
El mecanismo de Fail-Fast es un mecanismo de detección de errores. Solo se puede usar para detectar errores, porque JDK no garantiza que el mecanismo de fallas se suceda. Si utiliza una colección de mecanismo de Fail-Fast en un entorno multiproceso, se recomienda usar "clases de" paquete java.util.concurrent "para reemplazar" las clases en el paquete java.util ".
Por lo tanto, en este ejemplo, solo necesita reemplazar ArrayList con la clase correspondiente en el paquete java.util.concurrent. Es decir, el código
Lista estática privada <String> list = new ArrayList <String> ();
Reemplazar con
Lista estática privada <String> list = new CopyOnWriteArrayList <String> ();
Esta solución se puede resolver.
4. Principio de falla rápido
Se genera el evento de Fail-Fast, que se activa al lanzar una excepción concurrente de Modificación Excepción.
Entonces, ¿cómo lanza ArrayList una excepción concurrente de modificación.
Sabemos que concurrentModificationException es una excepción lanzada al operar iterador. Primero echemos un vistazo al código fuente de Iterator. El iterador de ArrayList se implementa en la clase principal abstractList.java. El código es el siguiente:
paquete java.util;
Public Abstract Class AbstractList <E> extiende AbstractCollection <E> Lista de implementos <E> {... // El atributo único en AbstractList // utilizado para registrar el número de lista modificada: CADA TIEMPO (Operaciones Agregar/Eliminar, etc.), ModCount+1 protegido transiental int Modcount = 0; // Devuelve el iterador para la lista. De hecho, es devolver el objeto ITR. public ITerator <E> iterator () {return new itr (); } // ITR es la clase de implementación del iterador (iterador). clase privada ITR implementa iterador <E> {int cursor = 0; int lastret = -1; // Modificar el valor de registro del número. // Cada vez que se crea un nuevo objeto ITR (), se guardará el ModCount correspondiente cuando se cree el nuevo objeto; // Cada vez que atraviese los elementos en la lista, comparará si los esperados MODCOUNT y MODCOUNT son iguales; // Si no es igual, se lanza una excepción concurrente de Modificación Excepción, lo que resulta en un evento fallido. int esperadoModCount = modCount; public boolean Hasnext () {return cursor! = size (); } public e Next () {// Antes de obtener el siguiente elemento, se juzgará si el "ModCount guardado al crear un nuevo objeto ITR" y "ModCount actual" son iguales; // Si no es igual, se lanza una excepción concurrente de Modificación Excepción, lo que resulta en un evento fallido. checkforcomodification (); intente {e next = get (cursor); Lastret = cursor ++; regresar a continuación; } Catch (indexOuTOfBoundsexception e) {checkforcomodification (); tirar nueva nosuchelementException (); }} public void remove () {if (lastret == -1) tirar nueva ilegalStateException (); checkforcomodification (); intente {abstractList.this.remove (Latret); if (lastret <cursor) cursor--; Lastret = -1; esperadoModCount = modCount; } Catch (indexOuTOfBoundSException e) {tirar nueva concurrenteModificationException (); }} final void checkForComOdification () {if (modCount! = EsperadoModCount) tire nuevo concurrentModificationException (); }} ...} A partir de esto, podemos encontrar que checkForComodification () se ejecuta cuando se llama a Next () y eliminar (). Si "ModCount no es igual a esperadoModCount", se lanza una excepción concurrente de Modificación Excepción, lo que resulta en un evento fallido.
Para comprender el mecanismo de fastidio, debemos entender cuándo "ModCount no es igual a los esperados.
De la clase ITR, sabemos que los esperados Modcount se asignan a ModCount al crear el objeto ITR. A través de ITR, sabemos que los esperados MODCOUNT no se pueden modificar para no igual a ModCount. Por lo tanto, lo que debe verificarse es cuando ModCount se modificará.
A continuación, verifiquemos el código fuente de ArrayList para ver cómo se modifica ModCount.
paquete java.util; public class ArrayList <E> extiende AbstractList <E> Lista de implementos <E>, RandomAccess, Clonable, java.io.serializable {... // Cuando la capacidad cambia en la lista, la función de sincronización correspondiente public void ssurecapacity (int mincapacity) {modcunt ++; int if (mincapacity> OldCapacity) {Object OldData [] = elementData; int newCapacity = (OldCapacity * 3)/2 + 1; if (newCapacity <mincapacity) newCapacity = mincapacity; // La mincapacidad generalmente está cerca del tamaño, por lo que esta es una victoria: elementData = arrays.copyOf (elementData, newCapacity); }} // Agregar elemento al último de la cola public boolean add (e e) {// modificar modcount setureCapacity (tamaño + 1); // incrementa modcount! elementData [size ++] = e; devolver verdadero; } // Agregar elemento a la ubicación especificada public void add (int index, e elemento) {if (index> size || índice <0) tirar nueva indexOutofBoundsexception ("índice:"+index+", tamaño:"+tamaño); // Modificar modCount EnsureCapacity (tamaño+1); // incrementa modcount! System.ArrayCopy (ElementData, índice, elementData, índice + 1, tamaño - índice); elementData [index] = elemento; tamaño ++; } // Agregar colección public boolean addall (colección <? Extiende e> c) {objeto [] a = c.toarray (); int numnew = A.Length; // modificar modcount ssurecapacity (tamaño + numnew); // incrementa modCount System.ArrayCopy (a, 0, elementData, tamaño, numnew); tamaño += numnew; devuelve numnew! = 0; } // eliminar el elemento en la ubicación especificada public e remove (int index) {rangeCheck (index); // modificar modcount modcount ++; E OldValue = (E) ElementData [índice]; int numMoved = tamaño - índice - 1; if (numMoved> 0) System.ArrayCopy (ElementData, índice+1, elementData, index, numMoved); elementData [-size] = null; // Deja que GC haga su trabajo devuelve OldValue; } // Eliminar rápidamente los elementos en la ubicación especificada privada void fastremove (int index) {// modificar modcount modcount ++; int numMoved = tamaño - índice - 1; if (numMoved> 0) System.ArrayCopy (ElementData, índice+1, elementData, index, numMoved); elementData [-size] = null; // deja que GC haga su trabajo} // Borrar la colección public void clear () {// modificar modcount modcount ++; // deja que GC haga su trabajo para (int i = 0; i <size; i ++) elementData [i] = null; tamaño = 0; } ...} A partir de esto, descubrimos que si está add (), eliminar () o clare (), el valor de ModCount se cambiará siempre que implique modificar el número de elementos en el conjunto.
A continuación, solucionemos sistemáticamente cómo se produce Fail-Fast. Los pasos son los siguientes:
(01) Cree un nuevo nombre de ArrayList como ArrayList.
(02) Agregue contenido a ArrayList.
(03) Cree un nuevo "Hilo A" y lea el valor de ArrayList repetidamente a través del iterador en "Hilo A".
(04) Cree un nuevo "Hilo B" y elimine un "nodo A" en la ArrayList en "Hilo B".
(05) En este momento, ocurrirán eventos interesantes.
En algún momento, "Thread A" crea el iterador de la ArrayList. En este momento, "Node A" todavía existe en ArrayList. Al crear una lista de matrices, esperadoModCount = modCount (suponiendo que sus valores son n en este momento).
En algún momento durante el proceso de atravesar la ArrayList, se ejecuta "Subproceso B" y "Hild B" elimina el nodo A "en la ArrayList. Cuando "Subbrease B" se ejecuta eliminar () para la operación de eliminación, "ModCount ++" se ejecuta en remove (), y ModCount se convierte en n+1!
"Enhebre un" luego atraviesa. Cuando ejecuta la función Next (), se llama a CheckForComodification () para comparar los tamaños de "esperadoModCount" y "ModCount"; y "esperadoModCount = n", "modCount = n+1", por lo que se lanza una excepción de Excepción de Modificación concurrente, lo que resulta en un evento fallido.
¡En este punto, tenemos una comprensión completa de cómo se produce Fail-Fast!
Es decir, cuando múltiples subprocesos funcionan en el mismo conjunto, cuando un hilo accede al conjunto, el contenido del conjunto es cambiado por otros subprocesos (es decir, otros hilos cambian el valor de ModCount a través de ADD, Eliminar, Clear y otros métodos); En este momento, se lanzará una excepción concurrente de Modificación Excepción, lo que resulta en un evento de fallas.
5. El principio de resolver el fracaso rápido
Lo anterior explica los "métodos para resolver el mecanismo de falla-rápido" y también conoce la "causa raíz de la falla-rápida". A continuación, hablemos más sobre cómo resolver el evento Fail-Fast en el paquete java.util.concurrent.
Explicémoslo con el CopyOnWriteArrayList correspondiente a ArrayList. Primero echemos un vistazo al código fuente de CopyOnWriteArrayList:
paquete java.util.concurrent; import java.util.*; import java.util.concurrent.locks.*; import sun.misc.unsafe; public class CopyOnwritearRayList <E> Implementa la lista <E>, RandomAccess, clonable, java.io.serializable {... // regreso el itherator correspondiente a la colección pública. iterator () {return Los métodos de implementación de fallas rápidas en la nueva clase de colección son casi los mismos. Tomemos la lista de matrices más simple como ejemplo. transitorio protegido int modcount = 0; registra el número de veces que modificamos ArrayList. Por ejemplo, cuando llamamos add (), eliminar (), etc. para cambiar los datos, se cambiará ModCount ++. transitorio protegido int modcount = 0; registra el número de veces que modificamos ArrayList. Por ejemplo, cuando llamamos add (), eliminar (), etc. para cambiar los datos, se cambiará ModCount ++. Cowiterator <E> (getArray (), 0); } ... Claza estática privada Cowiterator <E> Implementa listiterator <E> {objeto final privado [] instantánea; privado int cursor; private Cowiterator (objeto [] elementos, int inicialcursor) {cursor = inicialCursor; // Al crear un nuevo Cowiterator, guarde los elementos en la colección en una nueva matriz de copias. // De esta manera, cuando cambia los datos del conjunto original, los valores en los datos de copia tampoco cambiarán. instantánea = elementos; } public boolean hasNext () {return cursor <snapshot.length; } public boolean Hasprevious () {return cursor> 0; } public e Next () {if (! HasNext ()) tire nuevo nosuchelementException (); return (e) instantánea [cursor ++]; } public e anterior () {if (! Hasprevious ()) tire nuevo nosuchelementException (); return (e) instantánea [-cursor]; } public int NextIndex () {return cursor; } public int anteriorindex () {return cursor-1; } public void remove () {lanzar nueva UnpportedOperationException (); } set public void (e e) {tirar nueva no compatible } public void add (e e) {tire nuevo sin apoyo a la operaciónxception (); }} ...} De esto podemos ver:
(01) A diferencia de ArrayList hereda de AbstractList, CopyOnWriteArrayList no hereda de AbstractList, solo implementa la interfaz de la lista.
(02) El iterador devuelto por la función iterator () de ArrayList se implementa en AbstractList; mientras que CopyOnWriteArrayList implementa el iterador mismo.
(03) Cuando NEXT () se llama en la clase de implementación de Iterator de ArrayList, "Llame a CheckforComodification () para comparar los tamaños de 'ExpectedModCount' y 'ModCount'"; Sin embargo, no existe la llamada checkForComodification () en la clase de implementación de Iterator de CopyOnWriteAryList, ¡y la concepción de Modificación concurrente no se lanzará!
6. Resumen
Dado que HashMap (ArrayList) no es seguro de subprocesos, si otros subprocesos modifican el mapa durante el proceso de usar el Iterator (la modificación aquí se refiere a la modificación estructural, no simplemente para modificar elementos del contenido de la colección), entonces se lanzará una Modificación Concurrente, que es, es decir, la estrategia de paso de fallas se implementa principalmente a través del campo ModCount para garantizar la visibilidad entre los subprocesos. Modcount es el número de modificaciones. Este valor se agregará a la modificación del contenido de HashMap (ArrayList). Luego, durante la inicialización del iterador, este valor se asignará a la cantidad esperada del iterador.
Pero el comportamiento fallido no está garantizado, por lo que la práctica de confiar en esta excepción es incorrecta