¿Por qué se lanza una excepción ConcurrentModificationException y cómo depurarla?
Estoy usando un Collection
(un HashMap
usado 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)
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 ConcurrentModificationException
cuando 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.
Intente usar un ConcurrentHashMap
en lugar de uno simpleHashMap
La mayoría de las clases no permiten la modificación de un Collection
while iterando mediante el Collection
uso 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.Iterator
Collection
Collection
Collection
Collection.iterator()
for
Iterator.next()
for
Collection
Para ayudar a los programadores, algunas implementaciones de esas Collection
clases intentan detectar modificaciones concurrentes erróneas y lanzan un mensaje ConcurrentModificationException
si 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 Collection
no siempre resulta en un error ConcurrentModificationException
.
La documentación de ConcurrentModificationException
dice:
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
ConcurrentModificationException
basá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
, HashMap
y 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
Iterator
archivoConcurrentModificationException
. 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
ConcurrentModificationException
basá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 Map
interfaz dice esto:
Las implementaciones no simultáneas deben anular este método y, en el mejor de los casos, generar un error
ConcurrentModificationException
si 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 errorIllegalStateException
si 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 ConcurrentModificationException
sugiere 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 Collection
lanzó la excepción (un método de la clase la habrá lanzado directa o indirectamente) y para qué Collection
objeto. Luego debes examinar desde dónde se puede modificar ese objeto.
- La causa más común es la modificación de
Collection
dentro de un bucle mejoradofor
sobre el archivoCollection
. ¡El hecho de que no vea unIterator
objeto en su código fuente no significa que no lo hayaIterator
allí! Afortunadamente, una de las declaraciones delfor
bucle 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
Collection
objeto. Tenga en cuenta que las vistas de colecciones no modificables (como las producidas porCollections.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 archivoCollection
, como sublistas ,Map
conjuntos de entradas yMap
conjuntos de claves , también conservan referencias al original (modificable)Collection
. Esto puede ser un problema incluso para un hilo seguroCollection
, comoCopyOnWriteList
; No asuma que las colecciones seguras para subprocesos (concurrentes) nunca pueden generar la excepción. - Las operaciones que pueden modificar un
Collection
pueden 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 Collection
objeto, para que sea más fácil evitar modificaciones simultáneas. Convierta el objeto en Collection
un private
objeto o una variable local y no devuelva referencias a él Collection
o sus iteradores desde los métodos. Entonces será mucho más fácil examinar todos los lugares donde se Collection
puede modificar. Si Collection
varios 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.