La modificación de un Collectiontiempo iterando a través de eso Collectionusando un noIterator está permitido por la mayoría de las Collectionclases. La biblioteca Java llama a un intento de modificar un Collectiontiempo 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 forbucle mejorado ), modificar el Collection, luego continuar iterando.
Para ayudar a los programadores, algunas implementaciones de esas Collectionclases intentan detectar modificaciones concurrentes erróneas y arrojan un ConcurrentModificationExceptionsi 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 Collectionno siempre resulta en un lanzamiento ConcurrentModificationException.
La documentación de ConcurrentModificationExceptiondice:
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 ConcurrentModificationExceptionde fallas se basan en el mejor esfuerzo.
Tenga en cuenta que
La documentación de la HashSet, HashMap, TreeSety ArrayListclases 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 Iteratorlanza 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 ConcurrentModificationExceptionbasan 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 Mapinterfaz dice esto:
Las implementaciones no concurrentes deben anular este método y, en el mejor esfuerzo, arrojar un ConcurrentModificationExceptionsi 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 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ía.
Tenga en cuenta nuevamente que solo se requiere una "base de mejor esfuerzo" para la detección, y a ConcurrentModificationExceptionse 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 Collectionlanzó la excepción (un método de la clase la habrá arrojado directa o indirectamente) y para qué Collectionobjeto. Luego debe examinar desde dónde se puede modificar ese objeto.
- La causa más común es la modificación de
Collectiondentro de un forbucle mejorado sobre Collection. ¡El hecho de que no vea un Iteratorobjeto en su código fuente no significa que no haya Iteratorallí! Afortunadamente, una de las declaraciones del forbucle 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
Collectionobjeto. 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 , 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 hilos (concurrentes) nunca pueden lanzar la excepción.
- Las operaciones que pueden modificar a
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 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 Collectionobjeto, para que sea más fácil evitar modificaciones concurrentes. Convierta Collectionun privateobjeto o una variable local y no devuelva referencias a Collectionsus iteradores de los métodos. Entonces es mucho más fácil examinar todos los lugares donde Collectionse pueden modificar. Si la Collectionvan a utilizar varios subprocesos, entonces es práctico asegurarse de que los subprocesos accedan Collectionsolo con la sincronización y el bloqueo adecuados.