util thread modification failed concurrentmodificationexception concurrent avoid java concurrentmodification

java - thread - La colección lanza o no lanza ConcurrentModificationException en función del contenido de la colección



java.util.concurrentmodificationexception null (5)

Respuesta corta

Porque el comportamiento a prueba de fallos de un iterador no está garantizado.

Respuesta larga

Está recibiendo esta excepción porque no puede manipular una colección mientras está iterando sobre ella, excepto a través del iterador.

Malo:

// we''re using iterator for (Iterator<String> i = c.iterator(); i.hasNext();) { // here, the collection will check it hasn''t been modified (in effort to fail fast) String s = i.next(); if(s.equals("lalala")) { // s is removed from the collection and the collection will take note it was modified c.remove(s); } }

Bueno:

// we''re using iterator for (Iterator<String> i = c.iterator(); i.hasNext();) { // here, the collection will check it hasn''t been modified (in effort to fail fast) String s = i.next(); if(s.equals("lalala")) { // s is removed from the collection through iterator, so the iterator knows the collection changed and can resume the iteration i.remove(); } }

Ahora al "por qué": en el código anterior, observe cómo se realiza la verificación de modificación: la eliminación marca la colección como modificada, y la siguiente iteración verifica cualquier modificación y falla si detecta que la colección ha cambiado. Otra cosa importante es que ArrayList (no está seguro de otras colecciones) no verifica la modificación en hasNext() .

Por lo tanto, dos cosas extrañas pueden suceder:

  • Si eliminas el último elemento mientras estás iterando, no se lanzará nada.
    • Esto se debe a que no hay un elemento "siguiente", por lo que la iteración termina antes de alcanzar el código de verificación de modificación
  • Si elimina el elemento del segundo al último, ArrayList.hasNext() también devolverá el valor false , porque el current index del iterador ahora apunta al último elemento (antes del segundo al último).
    • Así que incluso en este caso, no hay ningún elemento "siguiente" después de la eliminación

Tenga en cuenta que todo esto está en línea con la documentación de ArrayList :

Tenga en cuenta que el comportamiento a prueba de fallas de un iterador no se puede garantizar ya que, en términos generales, es imposible hacer ninguna garantía en presencia de modificaciones concurrentes no sincronizadas. Los iteradores rápidos de fallos lanzan ConcurrentModificationException con el mejor esfuerzo. Por lo tanto, sería incorrecto escribir un programa que dependiera de esta excepción para su corrección: el comportamiento a prueba de fallas de los iteradores se debe usar solo para detectar errores.

Editado para añadir:

Esta pregunta proporciona información sobre por qué la verificación de modificación concurrente no se realiza en hasNext() y solo se realiza en next() .

Esta pregunta ya tiene una respuesta aquí:

El siguiente código de Java lanza una ConcurrentModificationException , como se esperaba:

public class Evil { public static void main(String[] args) { Collection<String> c = new ArrayList<String>(); c.add("lalala"); c.add("sososo"); c.add("ahaaha"); removeLalala(c); System.err.println(c); } private static void removeLalala(Collection<String> c) { for (Iterator<String> i = c.iterator(); i.hasNext();) { String s = i.next(); if(s.equals("lalala")) { c.remove(s); } } } }

Pero el siguiente ejemplo, que difiere solo en el contenido de la Collection , se ejecuta sin ninguna excepción:

public class Evil { public static void main(String[] args) { Collection<String> c = new ArrayList<String>(); c.add("lalala"); c.add("lalala"); removeLalala(c); System.err.println(c); } private static void removeLalala(Collection<String> c) { for (Iterator<String> i = c.iterator(); i.hasNext();) { String s = i.next(); if(s.equals("lalala")) { c.remove(s); } } } }

Esto imprime la salida "[lalala]". ¿Por qué el segundo ejemplo no lanza una ConcurrentModificationException cuando el primer ejemplo lo hace?


No se puede eliminar de la lista si se está navegando con el bucle "para cada uno".

No puedes eliminar un elemento de una colección sobre la que estás iterando. Puede evitar esto utilizando explícitamente un Iterador y eliminando el elemento allí. Puedes usar Iterator.

Si utiliza el código de abajo, no obtendrá ninguna excepción:

private static void removeLalala(Collection<String> c) { /*for (Iterator<String> i = c.iterator(); i.hasNext();) { String s = i.next(); if(s.equals("lalala")) { c.remove(s); } }*/ Iterator<String> it = c.iterator(); while (it.hasNext()) { String st = it.next(); if (st.equals("lalala")) { it.remove(); } } }


Por otras respuestas, usted sabe cuál es la forma correcta de eliminar un elemento de la colección mientras está iterando la colección. Doy aquí la explicación de la pregunta básica. Y la respuesta a su pregunta se encuentra en el siguiente seguimiento de pila.

Exception in thread "main" java.util.ConcurrentModificationException at java.util.ArrayList$Itr.checkForComodification(Unknown Source) at java.util.ArrayList$Itr.next(Unknown Source) at com.ii4sm.controller.Evil.removeLalala(Evil.java:23) at com.ii4sm.controller.Evil.main(Evil.java:17)

En el stacktrace es obvio que i.next(); La línea arroja el error. Pero cuando solo tienes dos elementos en la colección.

Collection<String> c = new ArrayList<String>(); c.add("lalala"); c.add("lalala"); removeLalala(c); System.err.println(c);

Cuando se elimina el primero, i.hasNext() devuelve false y i.next() nunca se ejecuta para lanzar la excepción


Si observa el código fuente del iterador ArrayList (clase anidada privada Itr ), verá la falla en el código.

Se supone que el código es rápido, lo cual se realiza internamente en el iterador llamando a checkForComodification() , sin embargo, hasNext() no realiza esa llamada, probablemente por razones de rendimiento.

El hasNext() lugar es simplemente:

public boolean hasNext() { return cursor != size; }

Esto significa que cuando estás en el segundo último elemento de la lista, y luego eliminas un elemento (cualquier elemento), el tamaño se reduce y hasNext() piensa que estás en el último elemento (que no estabas), y devuelve false , omitiendo la iteración del último elemento sin error.

OOPS !!!!


debe eliminar del iterator (i) no la collection (c) directamente;

prueba esto:

for (Iterator<String> i = c.iterator(); i.hasNext();) { String s = i.next(); if(s.equals("lalala")) { i.remove(); //note here, changing c to i with no parameter. } }

EDITAR:

La razón por la que el primer intento lanza una excepción mientras que el segundo no es simplemente por el número de elementos en su colección.

como el primero pasará por el bucle más de una vez y el segundo solo se repetirá una vez. Por lo tanto, no tiene la posibilidad de lanzar excepción.