2009-10-15 12 views
8

No entiendo por qué obtengo una ConcurrentModificationException cuando repito este multimap. Leí el siguiente entry, pero no estoy seguro de haber entendido todo. Traté de agregar un bloque sincronizado. Pero mi duda es con qué sincronizar y cuándo.Guava MultiMap y ConcurrentModificationException

El multimap es un campo y ha creado así:

private Multimap<GenericEvent, Command> eventMultiMap = 
    Multimaps.synchronizedMultimap(HashMultimap.<GenericEvent, Command> create()); 

y se utiliza de esta manera:

eventMultiMap.put(event, command); 

y como esto (he intentado sincronizar esta parte del mapa, pero sin éxito)

for (Entry<GenericEvent, Command> entry : eventMultiMap.entries()) { 
    if (entry.getValue().equals(command)) { 
     eventMultiMap.remove(entry.getKey(), entry.getValue()); 
     nbRemoved++; 
    } 
} 
+0

Véase también http://stackoverflow.com/questions/1675037/removing-items-from-a-collection-in -java-while-iterating-over-it – finnw

Respuesta

1

En java8 también se puede utilizar un enfoque lambda:

eventMultiMap.entries().removeIf(genericEventCommandEntry -> genericEventCommandEntry.getValue().equals(command));

11

Llamar eliminar en una colección mientras está iterando a través de ella causar una ConcurrentModificationException cada vez, incluso si todo está hecho en el mismo subproceso: lo correcto es obtener un iterador explícito y llamar a .remove() sobre eso.

Editar: La modificación de su ejemplo:

Iterator<Map.Entry<GenericEvent, Command>> i = eventMultiMap.entries().iterator(); 
while (i.hasNext()) { 
    if (i.next().getValue().equals(command)) { 
     i.remove(); 
     nbRemoved++; 
    } 
} 
+0

Ok, entiendo el punto.Pero el iterador solo puede tomar un tipo de parámetro. No ? Si itero a través de los valores de mi mapa, y elimino uno de ellos. ¿Se borrará el valor correspondiente en el mapa original (eventMultiMap)? –

+0

Bien, el iterador en los valores del mapa puede eliminar los valores. Gracias. No puedo aceptar su respuesta por el momento, Iterator no está compilando, pero edítelo y lo aceptaré. –

+0

Lo siento. No tengo colecciones de google en el trabajo, por lo que no pude probar el código antes de publicarlo. Si están diseñados como el HashMap de Java, debería ser posible usar un iterador sobre los objetos de entrada. He editado para mostrar esto y lo verificaré dos veces cuando llegue a casa, si no has tenido la oportunidad de probarlo. mientras tanto. – MHarris

4

Si otro hilo podría modificar multimap mientras que esta lógica se está ejecutando, tendrá que añadir un bloque sincronizado con el código de MHarris:

synchronized (eventMultimap) { 
    Iterator<Entry<GenericEvent, Command>> i = eventMultiMap.entries.iterator(); 
    while (i.hasNext()) { 
    if (i.next().getValue().equals(command)) { 
     i.remove(); 
     nbRemoved++; 
    } 
    } 
} 

o bien, puede omitir el iterador de la siguiente manera,

synchronized (eventMultimap) { 
    int oldSize = eventMultimap.size(); 
    eventMultimap.values().removeAll(Collections.singleton(command)); 
    nbRemoved = oldSize - eventMultimap.size(); 
} 

el removeAll() llamada no hace requiere sincronización Sin embargo, si omite el bloque sincronizado, el multimap podría mutar entre la llamada removeAll() y una de las llamadas al tamaño(), lo que llevaría a un valor incorrecto de nbRemoved.

Ahora, si su código es de subproceso único y solo desea evitar una llamada a ConcurrentModificationException, puede omitir la lógica Multimaps.synchronizedMultimap y synchronized (eventMultimap).

+0

Me gusta removeAll (Collection.singleton (stuffToRemove)). Gracias. –

4

Es posible que desee ver this blogpost para otra trampa produciendo un ConcurrentModificationException al atravesar un multimapa, sin que interfiera ningún otro hilo. En resumen, si recorre las teclas de multimap, accede a la colección respectiva de valores asociados con cada clave y elimina algún elemento de dicha colección, si ese elemento es el último de la colección, va a tener ConcurrentModificationException cuando intente para acceder a la siguiente clave, porque vaciar una colección desencadena la eliminación de la clave, lo que modifica estructuralmente el conjunto de claves del multimap.

1

Prefiero Multimap.values().iterator() si no te importa la llave. También debe intentar evitar el uso de bloques sincronizados tanto como sea posible, ya que no puede priorizar las lecturas/escrituras con eficacia.

ReadWriteLock lock = new ReentrantReadWriteLock(); 
Lock writeLock = lock.writeLock(); 

public void removeCommands(Command value) { 
    try { 
    writeLock.lock(); 
    for (Iterator<Command> it = multiMap.values().iterator(); it.hasNext();) { 
     if (it.next() == value) { 
     it.remove(); 
     } 
    } 
    } finally { 
    writeLock.unlock(); 
    } 
}