Pregunta

En una aplicación multiproceso en la que estoy trabajando, ocasionalmente vemos ConcurrentModificationExceptions en nuestras Listas (que en su mayoría son ArrayList, a veces Vectores).Pero hay otras ocasiones en las que creo que se están produciendo modificaciones simultáneas porque parece que faltan elementos al iterar a través de la colección, pero no se generan excepciones.Sé que los documentos para ConcurrentModificationException dice que no puedes confiar en ello, pero ¿cómo puedo asegurarme de no modificar una lista al mismo tiempo?¿Y envolver cada acceso a la colección en un bloque sincronizado es la única forma de evitarlo?

Actualizar: Sí, lo sé Collections.synchronizedCollection, pero no protege contra que alguien modifique la colección mientras la recorre.Creo que al menos parte de mi problema ocurre cuando alguien agrega algo a una colección mientras la repito.

Segunda actualización Si alguien quiere combinar la mención de sincronizadaCollection y la clonación como lo hizo Jason con una mención de java.util.concurrent y los marcos de colecciones de Apache como lo hicieron jacekfoo y Javamann, puedo aceptar una respuesta.

¿Fue útil?

Solución

Su pregunta original parece solicitar un iterador que vea actualizaciones en vivo de la colección subyacente sin dejar de ser seguro para subprocesos.Este es un problema increíblemente costoso de resolver en el caso general, razón por la cual ninguna de las clases de colección estándar lo hace.

Hay muchas formas de lograr soluciones parciales al problema y, en su aplicación, una de ellas puede ser suficiente.

jason da una forma específica de lograr la seguridad de los subprocesos y evitar lanzar una excepción ConcurrentModificationException, pero sólo a expensas de la vivacidad.

Javamann menciona dos clases específicas en el java.util.concurrent paquete que resuelve el mismo problema sin bloqueos, donde la escalabilidad es crítica.Estos solo se entregaron con Java 5, pero ha habido varios proyectos que respaldan la funcionalidad del paquete en versiones anteriores de Java, incluido Éste, aunque no tendrán un rendimiento tan bueno en JRE anteriores.

Si ya está utilizando algunas de las bibliotecas de Apache Commons, entonces como jacekfoo Señala, el marco de colecciones de apache contiene algunas clases útiles.

También podrías considerar mirar el Marco de colecciones de Google.

Otros consejos

Dependiendo de su frecuencia de actualización, uno de mis favoritos es CopyOnWriteArrayList o CopyOnWriteArraySet.Crean una nueva lista/conjunto de actualizaciones para evitar excepciones de modificaciones simultáneas.

Verificar java.util.concurrente para versiones de las clases de Colecciones estándar que están diseñadas para manejar mejor la concurrencia.

Sí, tienes que sincronizar el acceso a los objetos de las colecciones.

Alternativamente, puede utilizar los contenedores sincronizados alrededor de cualquier objeto existente.Ver Colecciones.synchronizedCollection().Por ejemplo:

List<String> safeList = Collections.synchronizedList( originalList );

Sin embargo, todo el código debe usar la versión segura y, aun así, iterar mientras otro hilo modifica resultará en problemas.

Para resolver el problema de iteración, primero copie la lista.Ejemplo:

for ( String el : safeList.clone() )
{ ... }

Para obtener colecciones más optimizadas y seguras para subprocesos, consulte también java.util.concurrente.

Por lo general, obtiene una excepción ConcurrentModificationException si intenta eliminar un elemento de una lista mientras se repite.

La forma más sencilla de probar esto es:

List<Blah> list = new ArrayList<Blah>();
for (Blah blah : list) {
     list.remove(blah); // will throw the exception
}

No estoy seguro de cómo solucionarlo.Es posible que deba implementar su propia lista segura para subprocesos, o podría crear copias de la lista original para escribir y tener una clase sincronizada que escriba en la lista.

Podrías intentar usar copia defensiva para que las modificaciones a uno List no afecte a los demás.

Envolver los accesos a la colección en un bloque sincronizado es la forma correcta de hacerlo.La práctica de programación estándar dicta el uso de algún tipo de mecanismo de bloqueo (semáforo, mutex, etc.) cuando se trata de un estado compartido entre varios subprocesos.

Sin embargo, dependiendo de su caso de uso, generalmente puede realizar algunas optimizaciones para bloquear solo en ciertos casos.Por ejemplo, si tiene una colección que se lee con frecuencia pero que rara vez se escribe, puede permitir lecturas simultáneas pero aplicar un bloqueo cada vez que haya una escritura en curso.Las lecturas simultáneas solo causan conflictos si la colección está en proceso de modificación.

ConcurrentModificationException es el mejor esfuerzo porque lo que estás preguntando es un problema difícil.No existe una buena manera de hacer esto de manera confiable sin sacrificar el rendimiento, además de demostrar que sus patrones de acceso no modifican la lista simultáneamente.

La sincronización probablemente evitaría modificaciones simultáneas y puede que sea a lo que recurras al final, pero puede resultar costosa.Probablemente lo mejor que puedes hacer sea sentarte y pensar un rato en tu algoritmo.Si no puede encontrar una solución sin bloqueos, recurra a la sincronización.

Ver la implementación.Básicamente almacena un int:

transient volatile int modCount;

y eso se incrementa cuando hay una 'modificación estructural' (como eliminar).Si el iterador detecta que modCount cambió, lanza una excepción de modificación concurrente.

La sincronización (a través de Collections.synchronizedXXX) no funcionará ya que no garantiza la seguridad del iterador, solo sincroniza escrituras y lecturas mediante put, get, set...

Consulte java.util.concurennt y el marco de colecciones de Apache (tiene algunas clases optimizadas que funcionan correctamente en un entorno concurrente cuando hay más lecturas (que no están sincronizadas) que escrituras; consulte FastHashMap.

También puede sincronizar iteraciones sobre la lista.

List<String> safeList = Collections.synchronizedList( originalList );

public void doSomething() {
   synchronized(safeList){
     for(String s : safeList){
           System.out.println(s);

     }
   }

}

Esto bloqueará la lista en sincronización y bloqueará todos los hilos que intenten acceder a la lista mientras la edita o la itera.La desventaja es que creas un cuello de botella.

Esto ahorra algo de memoria sobre el método .clone() y podría ser más rápido dependiendo de lo que estés haciendo en la iteración...

Colecciones.synchronizedList() generará una lista nominalmente segura para subprocesos y java.util.concurrent tiene características más potentes.

Esto eliminará la excepción de modificación simultánea.Sin embargo, no hablaré de la eficiencia;)

List<Blah> list = fillMyList();
List<Blah> temp = new ArrayList<Blah>();
for (Blah blah : list) {
     //list.remove(blah);  would throw the exception
     temp.add(blah);
}
list.removeAll(temp);
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top