java - thread - ConcurrentModificationException cuando se usa la secuencia con el conjunto de teclas de Mapas
java.util.concurrentmodificationexception: null (4)
@Eran ya explained cómo resolver mejor este problema. Explicaré por qué ocurre ConcurrentModificationException
.
La ConcurrentModificationException
produce porque está modificando el origen de la secuencia. Es probable que su Map
sea HashMap
o TreeMap
u otro mapa no concurrente. Supongamos que es un HashMap
. Cada secuencia está respaldada por Spliterator
. Si el separador no tiene características IMMUTABLE
y CONCURRENT
, entonces, como dice la documentación:
Después de unir un Spliterator, en el mejor esfuerzo, debe lanzar
ConcurrentModificationException
si se detecta una interferencia estructural. Los separadores que hacen esto se llaman fall-fast .
Por lo tanto, HashMap.keySet().spliterator()
no es IMMUTABLE
(porque este Set
puede modificarse) y no es CONCURRENT
(las actualizaciones simultáneas no son seguras para HashMap
). Así que solo detecta los cambios simultáneos y lanza una ConcurrentModificationException
como prescribe la documentación del separador.
También vale la pena citar la documentación de HashMap
:
Los iteradores devueltos por todos los "métodos de vista de colección" de esta clase son a prueba de fallos : si el mapa se modifica estructuralmente en cualquier momento después de que se crea el iterador, de cualquier manera, excepto a través del propio método de eliminación del iterador, el iterador lanzará una
ConcurrentModificationException
. Por lo tanto, ante una modificación concurrente, el iterador falla de manera rápida y limpia, en lugar de arriesgarse a comportamientos arbitrarios y no deterministas 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 ninguna garantía en presencia de modificaciones concurrentes no sincronizadas. Los iteradores a prueba de fallos lanzan
ConcurrentModificationException
el mejor esfuerzo. Por lo tanto, sería incorrecto escribir un programa que dependiera de esta excepción para su corrección: el comportamiento a prueba de fallas de los iteradores se debe usar solo para detectar errores .
Mientras que dice acerca de los iteradores solamente, creo que es lo mismo para los separadores.
Quiero eliminar todos los elementos de someMap
que teclas no están presentes en someList
. Echa un vistazo a mi código:
someMap.keySet().stream().filter(v -> !someList.contains(v)).forEach(someMap::remove);
Recibo java.util.ConcurrentModificationException
. ¿Por qué? La corriente no es paralela. ¿Cuál es la forma más elegante de hacer esto?
No necesitas la API de Stream
para eso. Utilice retainAll
en el conjunto de keySet
. Cualquier cambio en el Set
devuelto por keySet()
se refleja en el Map
original.
someMap.keySet().retainAll(someList);
Responda más tarde, pero puede insertar un colector en su canalización para que forEach esté operando en un Conjunto que contiene una copia de las claves:
someMap.keySet()
.stream()
.filter(v -> !someList.contains(v))
.collect(Collectors.toSet())
.forEach(someMap::remove);
Su llamada de secuencia está (lógicamente) haciendo lo mismo que:
for (K k : someMap.keySet()) {
if (!someList.contains(k)) {
someMap.remove(k);
}
}
Si ejecuta esto, encontrará que genera ConcurrentModificationException
, porque está modificando el mapa al mismo tiempo que lo está iterando sobre él. Si echa un vistazo a los docs , notará lo siguiente:
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étodo que viola el contrato de un objeto, el objeto puede lanzar esta excepción. Por ejemplo, si un hilo modifica una colección directamente mientras está iterando sobre la colección con un iterador rápido, el iterador lanzará esta excepción.
Esto es lo que está haciendo, la implementación del mapa que está utilizando tiene, evidentemente, iteradores rápidos, por lo que se está lanzando esta excepción.
Una posible alternativa es eliminar los elementos utilizando el iterador directamente:
for (Iterator<K> ks = someMap.keySet().iterator(); ks.hasNext(); ) {
K next = ks.next();
if (!someList.contains(k)) {
ks.remove();
}
}