style="display:inline-block;width:728px;height:90px"
data-ad-client="ca-pub-5164839828746352"
data-ad-slot="7563230308">

Cómo manejar la ConcurrentModificationException

Bueno, en un proyecto que estaba usando ArrayList y me encuentro que al hacer uso de nuestro querido y bien ponderado foreach (lo pongo en código):

ArrayList<MiClase> miclases = new ArrayList<MiClase>();
for(MiClase c : miclases){
 if(c == otrobjeto)
    miclases.remove(c);
}

¿Qué pasa?. Esto nos arroja una desagradable y nada querida ConcurrentModificationException. Existe la forma de usar la clase Iterator para evitarlo, pero: "A mi me gusta foreach, ¿tengo que usar Iterator nomás por esto?", si bien eres de los míos podemos usar en lugar de la clase ArrayList, la clase CopyOnWriteArrayList, dejando:

CopyOnWriteArrayList<MiClase> miclases = new CopyOnWriteArrayList<MiClase>();
for(MiClase c : miclases){
 if(c == otrobjeto)
    miclases.remove(c);
}

¡Y PIMBA!, nos quitamos de esa desagradable excepción. Saludos y espero que les sirva.
Les dejo esta liga de un artículo donde encontré esto pero en inglés.

Saludos.

Comentarios

Opciones de visualización de comentarios

Seleccione la forma que prefiera para mostrar los comentarios y haga clic en «Guardar las opciones» para activar los cambios.

No sucede por arte de magia...

Si bien el resultado efectivamente _evita_ un ConcurrentModificationException, no significa que simplemente la solución consiste en reemplazar una naranja podrida por una naranja agria. Permíteme explicar mi analogía.

Tu trozo de código inicial no indica que se ejecute en diferentes threads de control, y si así lo fuese, el ejemplo ni si quiera provocaría dicho comportamiento a menos que ambos intenten modificar el estado de tu ArrayList; por lo que tienes un error al intentar reproducir el ejemplo obtenido de la página que muestras al final. Es indispensable comprender la naturaleza de un ConcurrentModificationException y bajo qué condiciones es arrojada dicha excepción. Cuando hablo de "naranja podrida", me refiero al hecho de que es sabido que un ArrayList es una estructura mutable, no segura para modificaciones concurrentes o ante la presencia de múltiples threads de control, lo que significa que necesitas de mecanismos para sincronizar el acceso a dicha estructura. También es sabido que aunque reemplaces tu ArrayList por Collections.synchronizedList o Vector en su defecto, no garantiza tampoco un estado deseable ante operaciones concurrentes compuestas sobre la estructura misma. Pero, de dónde se origina entonces un ConcurrentModificationException? Pues del hecho de que mientras un thread de control está modificando el estado de una estructura (o colección en este caso), otro se encuentra iterando sobre ella y de pronto se quiere modificar el estado durante la iteración y dicho bloque compuesto no esté protegido (sincronizado) mediante algún lock para garantizar visibilidad de memoria a un solo thread. Se dice que para evitar un estado inconsistente en los datos, los iteradores son de tipo fail-fast y arrojarán tal excepción en cuanto se detecte el caso antes mencionado. Imagina que es lo mismo que un IllegalMonitorStateException pero aplicado a un iterador y una colección específica.

A qué me refiero con "naranja agria" cuando hablo de CopyOnWriteArrayList? Definitivamente no al hecho de que sea una mala solución, sino al hecho de que sigue siendo una naranja, pero semánticamente funciona un poco diferente, y mientras esos cambios semánticos no interfieran realmente con el comportamiento que esperas de tu algoritmo y/o funcionalidad, entonces no importa. Caso contrario a HashMap vs ConcurrentHashMap, pero queda fuera del contexto de mi ejemplo. CopyOnWriteArrayList utiliza una técnica muy famosa en el campo de OSDev para hacer copia de páginas de memoria cuando creas un nuevo proceso (fork), entre otros casos. Lo que garantiza es que mientras nadie quiera modificar el estado de tu estructura, la memoria será compartida, pero una vez que exista un atentado de modificación, los datos serán copiados de manera que lo que obtienes en un iterador sobre dicha estructura es un snapshot (o muestra) de lo que tenía la estructura en el momento que creaste el iterador. Por lo tanto, cambios que hagas sobre dicho iterador no serán aplicados (ni siquiera son permitidas dichos métodos), y cambios hechos a la estructura mediante otro mecanismo tampoco serán reflejados en tu iterador.

Cómo podrás observar, no es simplemente cambiar una estructura por otra y esperar que las cosas funcionen de manera automática, siempre tiene uno que detenerse y analizar lo que ofrecen las APIs para casos específicos. Para la mayoría de los casos donde se requiere acceso concurrente y alta visibilidad de memoria sobre dicha estructura crear un synchronizedList puede ser suficiente, pero si el rendimiento se ve afectado por eso y no se requiere tener visibilidad en tiempo real de la estructura al estarla mutando constantemente, entonces CopyOnWriteArrayList puede ser el indicado.

Re: No sucede por arte de magia

Dispense usted, pido porfavor intente el primer trozo de código y verá cómo es que esta excepción ocurre, incluso en la liga que he puesto de otra persona en donde he encontrado la solución viene un extracto de código parecido y en ningún lugar que he visto a cerca del tema (en donde se involucran listas) dice algo de estar en diferentes o mismo thread. Pido también ser capaz de leer:

...en un proyecto que estaba usando ArrayList...

¿Será qué mi proyecto no podrá llevar sentencias en varios threads o es qué conoces tan bien mi proyecto?. Sólo puse el extracto de código qué arroja el error, por si a alguien le llega a ocurrir.

Y tu solo te has respondido:

Para la mayoría de los casos donde se requiere acceso concurrente y alta visibilidad de memoria sobre dicha estructura crear un synchronizedList puede ser suficiente, pero si el rendimiento se ve afectado por eso y no se requiere tener visibilidad en tiempo real de la estructura al estarla mutando constantemente, entonces CopyOnWriteArrayList puede ser el indicado.

No lo usé nada más así al chilazo, estuve buscando leyendo API que diferencias hay entre las más de 1000 y 1 colecciones de Java. Después de darme un paseo por acá, investigando y llegando al blog (el de la liga que puse) en donde me encontré con un ejemplo de CopyOnWriteArrayList y que abordaba mi problema.

Otra cosa, no dije que fuera la única solución:

Existe la forma de usar la clase Iterator para evitarlo

Esto funciona por si te gusta la implementación ArrayList, y si te gusta el uso de foreach. Aquí no se dice que es mágico ni mucho menos, se resume para que cualquier persona lo pueda entender, el sabio y que empieza; así no se me escapa nadie.

Saludos.

No hay problema...

Qué bueno que lo comprendas muy bien entonces. También se ve que sabes que foreach es una simple transformación en tiempo de compilación de un Iterator. Pero dejemos el tema en que leí mal tu ejemplo, y en que puesto a que no conozco tu proyecto no puedo asumir no sé que cosas en cuanto a threads ni programación concurrente en general y que mi cátedra definitivamente no te sirvió :) Pero también recuerda que tanto tu post, como mi comentario no es leido por tí o por mí solamente, sino por más personas que participan en el foro, y que así como tú, quisieran aprender esos detalles que tú al parecer aprendiste de la URL que publicaste y de tu ardua investigación. Y que tampoco el autor de la URL explica bajo que criterios existen dichas colecciones y excepciones que mencionas y que por tanto me dí a la tarea de explicar.

Lección a aprender es que no tomes cualquier comentario como ataque hacía tí, en ningún momento dije que tu solución fuera la peor, o que asumía que conozco lo que estabas haciendo o no haciendo, lee nuevamente y encontrarás que puse "No es que sea mala solución". No es necesario ser una diva cuando se trata de compartir conocimiento con la gente ya sea que alguien te intente corregir o simplemente enseñar algo que muy posiblemente no sabes.

Imagen de VictorManuel

=)

Todas las criticas son nutritivas ;)

Re: No hay problema...

Es que como te dije, (pero por lo que veo lo has tomado a mal o de manera sarcástica) este post se resume...Es sólo la solución, a mucha gente de nada le sirve saber que las listas están compuestas de nodos y que estos a su vez tienen otro nodo, según el número de nodos se les da el nombre de enlazadas o listas dobles, etc....Se asume (de parte mía) que ya se sabe lo que es una lista y las diferencias, este post es cómo un: "Ah!, si cierto se me había olvidado", no un: "¿para qué casos es mejor utilizar cada colección?", si fuera lo segundo estás de acuerdo que tendría que ser todo un tema entero (preferiblemente extraído de varias fuentes confiables), o una saga de temas cómo muchos ponen por acá: "Primera entrega de [inserte aquí algún tópico]". Era eso.

Con lo del foreach, de hecho es un iterador, sólo que (al menos así funciona en C#) la clase misma tiene un enumerador, que no es más que un iterador con un método que "retorna" valores mientras existan en una colección.

Mi comentario, como dije no es para que lo tomarán a mal. No me sentí atacado, quizás a veces "se me desata" y estas cosas pasan (espero y no vuelva a ocurrir :).
Saludos.

Imagen de AlexSnake

Lo probaré

Hace unos días me pasó algo similar y para resolver ese problema tuve que crear dos listas, para pasar el contenido de una a otra. No es una solución muy buena pero era urgente resolver eso.

Me acaba de surgir otra duda, que pasa si en vez de querer eliminar el objeto quieres agregar uno? Servirá de la misma forma? Porque finalmente te va arrojar una excepción.

for(MiClase c : miclases){
 if(c == otrobjeto)
    miclases.add(c);
}

RE: Lo probaré

Pues no creo que nadie sea tan enfermo cómo para hacer eso (duplicar elementos en una lista, ah no ser que la especificación así lo diga). Pero, según la teoría estás en lo correcto, se supone debe arrojar la excepción.

Imagen de AlexSnake

No precisamente

Yo me referia que si tienes dos listas del mismo tipo de objeto y las anidas para comparar objetos para agregar el que no se encuentre es ahí donde puede entrar una excepcion, pero pss gracias por resolver la duda. Salu2.

Imagen de ezamudio

duplicar listas

Si tienes una propiedad en tu objeto que es una lista y tienes un getter pero no quieres dar la lista original para que no te la vayan a modificar, entonces puedes duplicarla y devolver la copia, de modo que si le agregan o quitan objetos no importa (a menos que devuelvas una copia inmutable):

public List<?> getMiLista() {
  //Esta no la podran modificar
  return Collections.unmodifiableList(miLista);
}
public List<?> getMiOtraLista() {
  //Esta la pueden modificar pero no afecta mi lista interna
  return new ArrayList<?>(miOtraLista);
}

Re: No precisamente

Lo que yo haría sería:

for(MiClase c : miclases)
   if(!otralista.contains(c))
      otralista.add(c);

Así verificas si existe y de no existir añades el objeto...recuerda que miclases puede ser un query (mi caso) y otralista puede ser quizás "usuarios que están actualmente usando la aplicación".

¿Entendí bien tu comentario?

Re: duplicar listas

¿Para eso se puede retornar un clone o copy qué no?...En código:

public List<MiClase> getmiclases(){
    return miclases.copy(); //No recuerdo si es copy o clone.
}
Imagen de ezamudio

clone()

Es clone() y pues depende de la clase que sea la lista. La interfaz List como tal, no extiende Cloneable. ArrayList y algunas otras implementaciones sí implementan Cloneable, haciendo una copia superficial (se clona la lista pero con los mismos elementos).

Re: clone()

Jeje, eso de andar traduciendo de C# a Java no deja nada bueno XD. Y si, pues es la implementación de la que hablamos la ArrayList y la CopyOnWriteArrayList.

Imagen de AlexSnake

Comentario acertado

Así es, esa es la finalidad y sobre la duplicidad de las listas no sabia que se podia hacer eso, de igual forma son cosas nuevas que habrá que implementar para ocupar posteriormente.

style="display:inline-block;width:728px;height:90px"
data-ad-client="ca-pub-5164839828746352"
data-ad-slot="7563230308">