2008-09-16 9 views
7

En una aplicación de subprocesos múltiples en la que estoy trabajando, ocasionalmente vemos ConcurrentModificationExceptions en nuestras listas (que en su mayoría son ArrayList, a veces vectores). Pero hay otras veces en las que creo que ocurren modificaciones concurrentes porque parece que la iteración a través de la colección está perdiendo elementos, pero no se lanzan excepciones. Sé que los documentos para ConcurrentModificationException dicen que no puedes confiar en él, pero ¿cómo me aseguraré de no modificar una Lista al mismo tiempo? ¿Y está envolviendo cada acceso a la colección en un bloque sincronizado la única manera de prevenirlo?¿Detecta modificaciones concurrentes?

Actualización: Sí, sé de Collections.synchronizedCollection, pero no protege contra que alguien modifique la colección mientras la itera. Creo que al menos parte de mi problema está sucediendo cuando alguien agrega algo a una colección mientras lo estoy recorriendo.

Segundo informe de actualización Si alguien quiere combinar la mención de la synchronizedCollection y la clonación como Jason hizo con una mención de la java.util.concurrent y las colecciones marcos Apache como jacekfoo y Javamann hizo, puede aceptar una respuesta.

+0

RE descarga: una combinación del uso de una colección sincronizada y la duplicación de contenido de la lista antes iterar es necesario. –

+0

Jason, sí veo eso ahora. Si agregara una mención de CopyOnWriteArrayList a su respuesta, la aceptaría. –

+0

¿Es importante que cuando esté iterando, la versión "en vivo" de la lista no se modifique, o puede iterar sobre una copia posiblemente obsoleta? Creo que también puede haber un problema de diseño (¿quién controla el acceso a la lista?), Junto con el uso de sincronización y java.util.concurrent. –

Respuesta

4

Su pregunta original parece estar pidiendo un iterador que vea actualizaciones en tiempo real de la colección subyacente mientras se mantiene seguro para subprocesos. Este es un problema increíblemente costoso de resolver en el caso general, por lo que ninguna de las clases de colección estándar lo hace.

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

Jason da a specific way to achieve thread safety, and to avoid throwing a ConcurrentModificationException, pero solo a expensas de la vitalidad.

Javamann menciona two specific classes en el paquete java.util.concurrent que resuelve el mismo problema sin bloqueos, donde la escalabilidad es fundamental. Estos solo se incluyen con Java 5, pero ha habido varios proyectos que respaldan la funcionalidad del paquete en versiones anteriores de Java, incluido el this one, aunque no tendrán un rendimiento tan bueno en los JRE anteriores.

Si ya está utilizando algunas de las bibliotecas de Apache Commons, entonces como jacekfoo points out, el apache collections framework contiene algunas clases útiles.

También podría considerar mirar el Google collections framework.

+0

Debe agregar que también hay un respaldo del paquete java.util.concurrent disponible. –

3

Consulte java.util.concurrent para ver las versiones de las clases de colecciones estándar que están diseñadas para manejar mejor la concurrencia.

1

Puede intentar usar la copia preventiva para que las modificaciones a una List no afecten a otras.

3

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

Como alternativa, puede utilizar los contenedores sincronizados alrededor de cualquier objeto existente. Ver Collections.synchronizedCollection(). Por ejemplo:

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

Sin embargo todo el código necesario para utilizar la versión segura, y aún así la iteración mientras que otro hilo modifica darán lugar a problemas.

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

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

Para más optimizado, colecciones de hilo de seguridad, también mira java.util.concurrent.

+0

¿Quiso decir Collections.synchronizedList(), verdad? No hay Collections.consurrentList(). – sblundy

+0

ArrayList usa un iterador internamente para hacer la copia. Envolver la lista original con una reducirá la ventana del problema, pero no lo eliminará. – sblundy

+0

Gracias, corrija ambos. clone() es seguro SI estás sincronizado. –

2

Normalmente obtiene una ConcurrentModificationException si está tratando de eliminar un elemento de una lista mientras se está iterando.

La forma más fácil 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 se obtendría alrededor de ella. Puede que tenga que implementar su propia lista de seguridad para subprocesos, o puede crear copias de la lista original para escribir y tener una clase sincronizada que escriba en la lista.

+0

Este problema ocurre incluso si solo tiene un hilo. Puedes evitarlo por cualquiera; 1) tome una copia de la lista antes de iterar sobre ella, 2) Use un Iterator.remove() para realizar la eliminación. Nota: las colecciones concurrentes no lanzan una excepción aquí. –

1

El ajuste de 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 múltiples hilos.

Dependiendo de su caso de uso, generalmente puede hacer algunas optimizaciones para bloquear solo en ciertos casos. Por ejemplo, si tiene una colección que se lee con frecuencia pero rara vez se escribe, entonces puede permitir lecturas simultáneas pero imponer un bloqueo cada vez que una escritura está en progreso. Las lecturas concurrentes solo causan conflictos si la colección está en proceso de modificación.

1

ConcurrentModificationException es el mejor esfuerzo porque lo que estás preguntando es un problema difícil. No hay una buena forma de hacerlo de manera confiable sin sacrificar el rendimiento además de probar que sus patrones de acceso no modifican la lista al mismo tiempo.

La sincronización probablemente evitará las modificaciones simultáneas, y puede ser a lo que recurra al final, pero puede terminar siendo costoso. Lo mejor que puede hacer es sentarse y pensar un rato sobre su algoritmo. Si no puede encontrar una solución sin bloqueo, recurra a la sincronización.

6

Según su frecuencia de actualización, uno de mis favoritos es CopyOnWriteArrayList o CopyOnWriteArraySet. Crean una nueva lista/conjunto de actualizaciones para evitar la excepción de modificación simultánea.

1

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.

Sincronización (a través de Collections.synchronizedXXX) no va a hacer el bien ya que no garantiza la seguridad iterador que sólo sincroniza escribe y lee a través de put, get, set ...

Ver java.util.concurennt y Apache framework de colecciones (tiene algunas clases que están optimizadas funcionan correctamente en un entorno concurrente cuando hay más lecturas (que no están sincronizadas) que escrituras - vea FastHashMap.

-1

Esto eliminará su excepción de modificación concurrente. No lo haré hablar a la eficiencia sin embargo;)

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); 
1

También puede sincronizar sobre iteratins 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 de sincronización y bloquear todas las discusiones que intentan acceder a la lista, mientras que modificarlo o iterar sobre ella. El inconveniente es que crea 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á haciendo en la iteración ...

Cuestiones relacionadas