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.