La modificación de un Collection
tiempo iterando a través de eso Collection
usando un noIterator
está permitido por la mayoría de las Collection
clases. La biblioteca Java llama a un intento de modificar un Collection
tiempo mientras itera a través de ella una "modificación concurrente". Desafortunadamente, eso sugiere que la única causa posible es la modificación simultánea por múltiples hilos, pero eso no es así. Usando solo un hilo es posible crear un iterador para el Collection
(usando Collection.iterator()
, o un bucle mejoradofor
), comenzar a iterar (usar Iterator.next()
, o ingresar de manera equivalente al cuerpo del for
bucle mejorado ), modificar el Collection
, luego continuar iterando.
Para ayudar a los programadores, algunas implementaciones de esas Collection
clases intentan detectar modificaciones concurrentes erróneas y arrojan un ConcurrentModificationException
si lo detectan. Sin embargo, en general no es posible y práctico garantizar la detección de todas las modificaciones concurrentes. Por lo tanto, el uso erróneo de la Collection
no siempre resulta en un lanzamiento ConcurrentModificationException
.
La documentación de ConcurrentModificationException
dice:
Esta excepción puede ser lanzada por métodos que han detectado la modificación concurrente de un objeto cuando tal modificación no está permitida ...
Tenga en cuenta que esta excepción no siempre indica que un objeto ha 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 lanzar esta excepción ...
Tenga en cuenta que el comportamiento a prueba de fallas no puede garantizarse ya que, en términos generales, es imposible hacer garantías duras en presencia de modificaciones concurrentes no sincronizadas. Las operaciones a prueba ConcurrentModificationException
de fallas se basan en el mejor esfuerzo.
Tenga en cuenta que
La documentación de la HashSet
, HashMap
, TreeSet
y ArrayList
clases dice lo siguiente:
Los iteradores devueltos [directa o indirectamente desde esta clase] son a prueba de fallas: si la [colección] se modifica en cualquier momento después de que se crea el iterador, de cualquier manera, excepto a través del método de eliminación propio del iterador, se Iterator
lanza a ConcurrentModificationException
. Por lo tanto, frente a la modificación concurrente, el iterador falla de manera rápida y limpia, en lugar de arriesgarse a un comportamiento arbitrario, no determinista en un momento indeterminado en el futuro.
Tenga en cuenta que el comportamiento a prueba de fallas de un iterador no puede garantizarse ya que, en términos generales, es imposible hacer garantías duras en presencia de modificaciones concurrentes no sincronizadas. Los iteradores a prueba de fallas se ConcurrentModificationException
basan en el mejor esfuerzo. Por lo tanto, sería incorrecto escribir un programa que dependiera de esta excepción para su corrección: el comportamiento rápido de los iteradores debe usarse solo para detectar errores .
Tenga en cuenta de nuevo que el comportamiento "no se puede garantizar" y solo es "en el mejor esfuerzo".
La documentación de varios métodos de la Map
interfaz dice esto:
Las implementaciones no concurrentes deben anular este método y, en el mejor esfuerzo, arrojar un ConcurrentModificationException
si se detecta que la función de mapeo modifica este mapa durante el cálculo. Las implementaciones concurrentes deberían anular este método y, en el mejor esfuerzo, arrojar un error IllegalStateException
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ía.
Tenga en cuenta nuevamente que solo se requiere una "base de mejor esfuerzo" para la detección, y a ConcurrentModificationException
se sugiere explícitamente solo para las clases no concurrentes (no seguras para subprocesos).
Depuración ConcurrentModificationException
Por lo tanto, cuando ve un seguimiento de pila debido a a ConcurrentModificationException
, no puede suponer de inmediato que la causa es el acceso no seguro de subprocesos múltiples a a Collection
. Debe examinar el seguimiento de la pila para determinar qué clase de Collection
lanzó la excepción (un método de la clase la habrá arrojado directa o indirectamente) y para qué Collection
objeto. Luego debe examinar desde dónde se puede modificar ese objeto.
- La causa más común es la modificación de
Collection
dentro de un for
bucle mejorado sobre Collection
. ¡El hecho de que no vea un Iterator
objeto en su código fuente no significa que no haya Iterator
allí! Afortunadamente, una de las declaraciones del for
bucle defectuoso generalmente estará en el seguimiento de la pila, por lo que rastrear 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 no modificables de las colecciones (como las producidas por Collections.unmodifiableList()
) retienen una referencia a la colección modificable, por lo que la iteración sobre una colección "no modificable" puede arrojar la excepción (la modificación se ha realizado en otro lugar). Otras vistas de su Collection
, como sublistas , Map
conjuntos de entradas y Map
conjuntos 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 hilos (concurrentes) nunca pueden lanzar la excepción.
- Las operaciones que pueden modificar a
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 la modificación concurrente por múltiples hilos.
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 concurrentes. Convierta Collection
un private
objeto o una variable local y no devuelva referencias a Collection
sus iteradores de los métodos. Entonces es mucho más fácil examinar todos los lugares donde Collection
se pueden modificar. Si la Collection
van a utilizar varios subprocesos, entonces es práctico asegurarse de que los subprocesos accedan Collection
solo con la sincronización y el bloqueo adecuados.