¿Por qué se lanza una excepción ConcurrentModificationException y cómo depurarla?

Resuelto mainstringargs asked hace 15 años • 8 respuestas

Estoy usando un Collection(un HashMapusado indirectamente por la JPA, da la casualidad), pero aparentemente de forma aleatoria el código arroja un archivo ConcurrentModificationException. ¿Qué lo está causando y cómo soluciono este problema? ¿Usando alguna sincronización, tal vez?

Aquí está el seguimiento completo de la pila:

Exception in thread "pool-1-thread-1" java.util.ConcurrentModificationException
        at java.util.HashMap$HashIterator.nextEntry(Unknown Source)
        at java.util.HashMap$ValueIterator.next(Unknown Source)
        at org.hibernate.collection.AbstractPersistentCollection$IteratorProxy.next(AbstractPersistentCollection.java:555)
        at org.hibernate.engine.Cascade.cascadeCollectionElements(Cascade.java:296)
        at org.hibernate.engine.Cascade.cascadeCollection(Cascade.java:242)
        at org.hibernate.engine.Cascade.cascadeAssociation(Cascade.java:219)
        at org.hibernate.engine.Cascade.cascadeProperty(Cascade.java:169)
        at org.hibernate.engine.Cascade.cascade(Cascade.java:130)
mainstringargs avatar Mar 02 '09 21:03 mainstringargs
Aceptado

Este no es un problema de sincronización. Esto ocurrirá si la colección subyacente sobre la que se está iterando es modificada por algo que no sea el propio iterador.

Iterator it = map.entrySet().iterator();
while (it.hasNext()) {
    Entry item = it.next();
    map.remove(item.getKey());
}

Esto generará un mensaje ConcurrentModificationExceptioncuando se it.hasNext()llame por segunda vez.

El enfoque correcto sería

Iterator it = map.entrySet().iterator();
while (it.hasNext()) {
    Entry item = it.next();
    it.remove();
}

Suponiendo que este iterador admita la remove()operación.

Robin avatar Mar 02 '2009 15:03 Robin

Intente usar un ConcurrentHashMapen lugar de uno simpleHashMap

Chochos avatar Mar 02 '2009 16:03 Chochos

La mayoría de las clases no permiten la modificación de un Collectionwhile iterando mediante el Collectionuso de an . La biblioteca Java llama a un intento de modificar un objeto mientras se itera a través de él una "modificación concurrente". Desafortunadamente, eso sugiere que la única causa posible es la modificación simultánea por parte de múltiples subprocesos, pero no es así. Usando solo un hilo es posible crear un iterador para (usando o un bucle mejorado ), comenzar a iterar (usando o equivalentemente ingresando el cuerpo del bucle mejorado), modificar y luego continuar iterando.IteratorCollectionCollectionCollectionCollection.iterator()forIterator.next()forCollection

Para ayudar a los programadores, algunas implementaciones de esas Collectionclases intentan detectar modificaciones concurrentes erróneas y lanzan un mensaje ConcurrentModificationExceptionsi lo detectan. Sin embargo, en general no es posible ni práctico garantizar la detección de todas las modificaciones simultáneas. Por lo tanto, el uso erróneo de Collectionno siempre resulta en un error ConcurrentModificationException.

La documentación de ConcurrentModificationExceptiondice:

Esta excepción puede ser lanzada por métodos que han detectado modificaciones simultáneas de un objeto cuando dicha modificación no está permitida...

Tenga en cuenta que esta excepción no siempre indica que un objeto haya sido modificado simultáneamente por un hilo diferente. Si un solo hilo emite una secuencia de invocaciones de métodos que viola el contrato de un objeto, el objeto puede generar esta excepción...

Tenga en cuenta que no se puede garantizar un comportamiento a prueba de fallos ya que, en términos generales, es imposible ofrecer garantías estrictas en presencia de modificaciones simultáneas no sincronizadas. Las operaciones rápidas se realizan ConcurrentModificationExceptionbasándose en el mejor esfuerzo.

Tenga en cuenta que

  • la excepción puede ser lanzada, no debe ser lanzada
  • no se requieren hilos diferentes
  • No se puede garantizar el lanzamiento de la excepción.
  • lanzar la excepción se hace sobre la base del mejor esfuerzo
  • lanzar la excepción ocurre cuando se detecta la modificación concurrente , no cuando es causada

La documentación de las clases HashSet, HashMapy dice esto TreeSet:ArrayList

Los iteradores devueltos [directa o indirectamente desde esta clase] son ​​rápidos: si la [colección] se modifica en cualquier momento después de crear el iterador, de cualquier manera excepto a través del propio método remove del iterador, arroja un Iteratorarchivo ConcurrentModificationException. Por lo tanto, ante una modificación concurrente, el iterador falla rápida y claramente, en lugar de correr el riesgo de comportarse arbitrariamente y no determinista en un momento indeterminado en el futuro.

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 garantías estrictas en presencia de modificaciones simultáneas no sincronizadas. Los iteradores a prueba de fallos se lanzan ConcurrentModificationExceptionbasándose en 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 rápido de los iteradores debería usarse sólo para detectar errores .

Tenga en cuenta nuevamente que el comportamiento "no se puede garantizar" y sólo se realiza "en el mejor de los casos".

La documentación de varios métodos de la Mapinterfaz dice esto:

Las implementaciones no simultáneas deben anular este método y, en el mejor de los casos, generar un error ConcurrentModificationExceptionsi se detecta que la función de mapeo modifica este mapa durante el cálculo. Las implementaciones simultáneas deben anular este método y, en el mejor de los casos, generar un error IllegalStateExceptionsi se detecta que la función de mapeo modifica este mapa durante el cálculo y, como resultado, el cálculo nunca se completará.

Tenga en cuenta nuevamente que solo se requiere el "mejor esfuerzo" para la detección, y se ConcurrentModificationExceptionsugiere explícitamente solo para las clases no concurrentes (no seguras para subprocesos).

DepuraciónConcurrentModificationException

Por lo tanto, cuando vea un seguimiento de pila debido a un archivo ConcurrentModificationException, no puede asumir inmediatamente que la causa es un acceso multiproceso inseguro a un archivo Collection. Debe examinar el seguimiento de la pila para determinar qué clase Collectionlanzó la excepción (un método de la clase la habrá lanzado directa o indirectamente) y para qué Collectionobjeto. Luego debes examinar desde dónde se puede modificar ese objeto.

  • La causa más común es la modificación de Collectiondentro de un bucle mejorado forsobre el archivo Collection. ¡El hecho de que no vea un Iteratorobjeto en su código fuente no significa que no lo haya Iteratorallí! Afortunadamente, una de las declaraciones del forbucle defectuoso normalmente estará en el seguimiento de la pila, por lo que localizar el error suele ser fácil.
  • Un caso más complicado es cuando su código pasa referencias al Collectionobjeto. Tenga en cuenta que las vistas de colecciones no modificables (como las producidas por Collections.unmodifiableList()) conservan una referencia a la colección modificable, por lo que la iteración sobre una colección "no modificable" puede generar la excepción (la modificación se ha realizado en otro lugar). Otras vistas de su archivo Collection, como sublistas , Mapconjuntos de entradas y Mapconjuntos de claves , también conservan referencias al original (modificable) Collection. Esto puede ser un problema incluso para un hilo seguro Collection, como CopyOnWriteList; No asuma que las colecciones seguras para subprocesos (concurrentes) nunca pueden generar la excepción.
  • Las operaciones que pueden modificar un Collectionpueden ser inesperadas en algunos casos. Por ejemplo, LinkedHashMap.get()modifica su colección .
  • Los casos más difíciles son cuando la excepción se debe a una modificación simultánea por parte de varios subprocesos.

Programación para evitar errores de modificación concurrentes

Cuando sea posible, limite todas las referencias a un Collectionobjeto, para que sea más fácil evitar modificaciones simultáneas. Convierta el objeto en Collectionun privateobjeto o una variable local y no devuelva referencias a él Collectiono sus iteradores desde los métodos. Entonces será mucho más fácil examinar todos los lugares donde se Collectionpuede modificar. Si Collectionvarios subprocesos van a utilizar el, entonces es práctico garantizar que los subprocesos accedan al Collectionúnico con la sincronización y el bloqueo adecuados.

Raedwald avatar Mar 13 '2019 11:03 Raedwald