Iteradores Fail Fast y Fail Safe en Java

En este artículo, voy a explicar cómo se comportan esas colecciones que no iteran tan rápido como fallan. En primer lugar, no existe un término a prueba de fallas dado en muchos lugares, ya que las especificaciones de Java SE no usan este término. Estoy usando a prueba de fallas para segregar entre iteradores Fail Fast y Non Fail Fast.
Modificación concurrente: Modificación concurrente en programación significa modificar un objeto concurrentemente cuando otra tarea ya se está ejecutando sobre él. Por ejemplo, en Java para modificar una colección cuando otro hilo está iterando sobre ella. Algunas implementaciones de Iterator (incluidas las de todas las implementaciones de colecciones de propósito general proporcionadas por JRE) pueden optar por lanzar ConcurrentModificationException si se detecta este comportamiento.
 

Iteradores Fail Fast y Fail Safe en Java

Los iteradores en Java se utilizan para iterar sobre los objetos de la colección. Los iteradores Fail-Fast lanzan inmediatamente ConcurrentModificationException si hay una modificación estructural de la colección. La modificación estructural significa agregar, eliminar cualquier elemento de la colección mientras un hilo itera sobre esa colección. Iterador en ArrayList, las clases HashMap son algunos ejemplos de iterador rápido.
Los iteradores a prueba de fallas no arrojan ninguna excepción si una colección se modifica estructuralmente mientras se itera sobre ella. Esto se debe a que operan en el clon de la colección, no en la colección original y por eso se les llama iteradores a prueba de fallas. Iterador en CopyOnWriteArrayList, las clases ConcurrentHashMap son ejemplos de iterador a prueba de fallas.
 

¿Cómo funciona Fail Fast Iterator?

Para saber si la colección se modifica estructuralmente o no, los iteradores rápidos usan un indicador interno llamado modCount que se actualiza cada vez que se modifica una colección. Los iteradores rápidos verifican el indicador modCount cada vez que obtiene el siguiente valor (es decir, usando next( ) ), y si encuentra que modCount se ha modificado después de que se haya creado este iterador, lanza ConcurrentModificationException .
 

Java

// Java code to illustrate
// Fail Fast Iterator in Java
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
  
public class FailFastExample {
    public static void main(String[] args)
    {
        Map<String, String> cityCode = new HashMap<String, String>();
        cityCode.put("Delhi", "India");
        cityCode.put("Moscow", "Russia");
        cityCode.put("New York", "USA");
  
        Iterator iterator = cityCode.keySet().iterator();
  
        while (iterator.hasNext()) {
            System.out.println(cityCode.get(iterator.next()));
  
            // adding an element to Map
            // exception will be thrown on next call
            // of next() method.
            cityCode.put("Istanbul", "Turkey");
        }
    }
}

Producción : 
 

India
Exception in thread "main" java.util.ConcurrentModificationException
    at java.util.HashMap$HashIterator.nextNode(HashMap.java:1442)
    at java.util.HashMap$KeyIterator.next(HashMap.java:1466)
    at FailFastExample.main(FailFastExample.java:18)

Puntos importantes de los iteradores rápidos: 
 

  • Estos iteradores lanzan ConcurrentModificationException si se modifica una colección mientras se itera sobre ella.
  • Utilizan la colección original para recorrer los elementos de la colección.
  • Estos iteradores no requieren memoria adicional.
  • Ej: iteradores devueltos por ArrayList, Vector, HashMap.

Nota 1 (de java-docs): No se puede garantizar el comportamiento a prueba de fallas de un iterador ya que, en términos generales, es imposible hacer garantías sólidas en presencia de modificaciones concurrentes no sincronizadas. Los iteradores a prueba de fallas lanzan ConcurrentModificationException según el mejor esfuerzo. Por lo tanto, sería un error escribir un programa que dependiera de esta excepción para su corrección: el comportamiento de falla rápida de los iteradores debería usarse solo para detectar errores.
Nota 2: si elimina un elemento a través del método Iterator remove() , no se generará una excepción. Sin embargo, en caso de eliminación a través de un método remove() de colección en particular, ConcurrentModificationExceptionserá arrojado. El siguiente fragmento de código demostrará esto:
 

Java

// Java code to demonstrate remove
// case in Fail-fast iterators
  
import java.util.ArrayList;
import java.util.Iterator;
  
public class FailFastExample {
    public static void main(String[] args)
    {
        ArrayList<Integer> al = new ArrayList<>();
        al.add(1);
        al.add(2);
        al.add(3);
        al.add(4);
        al.add(5);
  
        Iterator<Integer> itr = al.iterator();
        while (itr.hasNext()) {
            if (itr.next() == 2) {
                // will not throw Exception
                itr.remove();
            }
        }
  
        System.out.println(al);
  
        itr = al.iterator();
        while (itr.hasNext()) {
            if (itr.next() == 3) {
                // will throw Exception on
                // next call of next() method
                al.remove(3);
            }
        }
    }
}

Producción : 
 

[1, 3, 4, 5]
Exception in thread "main" java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
    at java.util.ArrayList$Itr.next(ArrayList.java:851)
    at FailFastExample.main(FailFastExample.java:28)

Iterador a prueba de fallas

En primer lugar, no existe un término a prueba de fallas dado en muchos lugares, ya que las especificaciones de Java SE no usan este término. Estoy usando este término para demostrar la diferencia entre el iterador Fail Fast y el Non-Fail Fast. Estos iteradores hacen una copia de la colección interna (arreglo de objetos) e iteran sobre la colección copiada. Cualquier modificación estructural realizada en el iterador afecta a la colección copiada, no a la colección original . Por lo tanto, la colección original permanece estructuralmente sin cambios
 

  • Los iteradores a prueba de fallas permiten modificaciones de una colección mientras se itera sobre ella.
  • Estos iteradores no arrojan ninguna excepción si se modifica una colección mientras se itera sobre ella.
  • Utilizan copia de la colección original para recorrer los elementos de la colección.
  • Estos iteradores requieren memoria adicional para la clonación de la colección. Ej: ConcurrentHashMap, CopyOnWriteArrayList

Ejemplo de iterador a prueba de fallas en Java:
 

Java

// Java code to illustrate
// Fail Safe Iterator in Java
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.Iterator;
  
class FailSafe {
    public static void main(String args[])
    {
        CopyOnWriteArrayList<Integer> list
            = new CopyOnWriteArrayList<Integer>(new Integer[] { 1, 3, 5, 8 });
        Iterator itr = list.iterator();
        while (itr.hasNext()) {
            Integer no = (Integer)itr.next();
            System.out.println(no);
            if (no == 8)
  
                // This will not print,
                // hence it has created separate copy
                list.add(14);
        }
    }
}

Producción: 
 

1
3
5
8

Además, aquellas colecciones que no usan el concepto de falla rápida no necesariamente pueden crear un clon/instantánea de él en la memoria para evitar ConcurrentModificationException. Por ejemplo, en el caso de ConcurrentHashMap, no opera en una copia separada aunque no es a prueba de fallas. En cambio, tiene una semántica que la especificación oficial describe como débilmente consistente (propiedades de consistencia de memoria en Java). El siguiente fragmento de código demostrará esto:
Ejemplo de iterador a prueba de fallas que no crea una copia separada 
 

Java

// Java program to illustrate
// Fail-Safe Iterator which
// does not create separate copy
import java.util.concurrent.ConcurrentHashMap;
import java.util.Iterator;
  
public class FailSafeItr {
    public static void main(String[] args)
    {
  
        // Creating a ConcurrentHashMap
        ConcurrentHashMap<String, Integer> map
            = new ConcurrentHashMap<String, Integer>();
  
        map.put("ONE", 1);
        map.put("TWO", 2);
        map.put("THREE", 3);
        map.put("FOUR", 4);
  
        // Getting an Iterator from map
        Iterator it = map.keySet().iterator();
  
        while (it.hasNext()) {
            String key = (String)it.next();
            System.out.println(key + " : " + map.get(key));
  
            // This will reflect in iterator.
            // Hence, it has not created separate copy
            map.put("SEVEN", 7);
        }
    }
}

Producción 
 

ONE : 1
FOUR : 4
TWO : 2
THREE : 3
SEVEN : 7

Nota (de java-docs) : los iteradores devueltos por ConcurrentHashMap son débilmente consistentes. Esto significa que este iterador puede tolerar modificaciones concurrentes, atraviesa elementos tal como existían cuando se construyó el iterador y puede (pero no se garantiza) reflejar modificaciones a la colección después de la construcción del iterador.
 

Diferencia entre el iterador Fail Fast y el iterador Fail Safe

La principal diferencia es que el iterador a prueba de fallas no arroja ninguna excepción, a diferencia del iterador a prueba de fallas. Esto se debe a que funcionan en un clon de la colección en lugar de la colección original y es por eso que se les llama iterador a prueba de fallas.
 

Publicación traducida automáticamente

Artículo escrito por mridul09 y traducido por Barcelona Geeks. The original can be accessed here. Licence: CCBY-SA

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *