2009-07-06 8 views
8

Actualmente estoy trabajando en una aplicación de subprocesos múltiples, y de vez en cuando recibo una excepción de modificación concurrente (aproximadamente una o dos veces por hora en promedio, pero que ocurre en intervalos aparentemente aleatorios).Excepción simultánea Excepción

La clase defectuosa es esencialmente un contenedor para un mapa, que se extiende a LinkedHashMap (con accessOrder configurado en verdadero). La clase tiene algunos métodos:

synchronized set(SomeKey key, SomeValue val) 

El método conjunto añade un par de claves/valor al mapa interno, y está protegido por la palabra clave sincronizada.

synchronized get(SomeKey key) 

El método get devuelve el valor en función de la tecla de entrada.

rebuild() 

El mapa interno se reconstruye de vez en cuando (~ cada 2 minutos, intervalos no coinciden con las excepciones). El método de reconstrucción esencialmente reconstruye los valores basados ​​en sus claves. Como Rebuild() es bastante caro, no puse una palabra clave sincronizada en el método. En cambio, yo estoy haciendo:

public void rebuild(){ 
    /* initialization stuff */ 
    List<SomeKey> keysCopy = new ArrayList<SomeKey>(); 
    synchronized (this) { 
    keysCopy.addAll(internalMap.keySet()); 
    } 
    /* 
    do stuff with keysCopy, update a temporary map 
    */  
    synchronized (this) { 
    internalMap.putAll(tempMap); 
    } 
} 

La excepción se produce en

keysCopy.addAll(internalMap.keySet()); 

Dentro del bloque sincronizado.

Las sugerencias son muy apreciadas. No dude en dirigirme a páginas/capítulos específicos en Java efectiva y/o Concurrencia en la práctica.

Actualización 1:

StackTrace Sanitized:

java.util.ConcurrentModificationException 
     at java.util.LinkedHashMap$LinkedHashIterator.nextEntry(LinkedHashMap.java:365) 
     at java.util.LinkedHashMap$KeyIterator.next(LinkedHashMap.java:376) 
     at java.util.AbstractCollection.toArray(AbstractCollection.java:126) 
     at java.util.ArrayList.addAll(ArrayList.java:473) 
     at a.b.c.etc.SomeWrapper.rebuild(SomeWraper.java:109) 
     at a.b.c.etc.SomeCaller.updateCache(SomeCaller.java:421) 
     ... 

Actualización 2:

Gracias a todos por las respuestas hasta el momento. Creo que el problema se encuentra dentro del LinkedHashMap y su atributo accessOrder, aunque no estoy del todo seguro (investigando).

Si accessOrder en un LinkedHashMap se establece en true, y acceder a su conjunto de claves a continuación, proceder a añadir el conjunto de claves a un LinkedList través addAll, realice una de estas acciones mutar el orden (es decir, cuentan para un "acceso")?

+0

OT, pero ¿no estás en peligro de perder cualquier cambio en la clase en la región comentada entre los dos métodos sincronizados en Rebuild()? – DaveR

+0

Sí, tienes toda la razón; pero de acuerdo con los documentos de diseño, es aceptable tener datos algo obsoletos :) – Cambium

+2

Maldita sea, ojalá tuviera documentos de diseño así;) – DaveR

Respuesta

7

Si construyó LinkedHashMap con accessOrder = true, LinkedHashMap.get() realmente muta el LinkedHashMap ya que almacena la entrada accedida más recientemente al principio de la lista de entradas enlazadas. Tal vez algo llama a get() mientras la lista de arreglos está haciendo su copia con el iterador.

+0

Suponiendo que nadie llama a get() mientras está haciendo la copia, ¿podría esto todavía activar la excepción? Aunque planteas un muy buen punto, examinaré las implementaciones de LinkedHashMap. Gracias! – Cambium

+0

Basado en su comentario anterior que utiliza accessOrder, y suponiendo que tiene que llamar a get() dentro de la función de reconstrucción (ya que todo lo que muestra son teclas de agarre), esto parece un posible culpable. ¿Las llamadas de reconstrucción() siempre se superponen? –

+0

Imagino que métodos como containsKey() o containsValue() también mutarían el mapa, pero la documentación de LinkedHashMap guarda silencio sobre este tema. –

0

Intente eliminar la palabra clave sincronizada de los métodos set() y get(), y en su lugar use un bloque sincronizado dentro del método, bloqueando en internalMap; luego cambie los bloques sincronizados en el método Rebuild() para bloquear también el InternalMap.

+0

Gracias por la sugerencia, ¿puede explicar un poco por qué eso podría resolver el problema? Voy a intentarlo en breve. – Cambium

+0

Dado que solo se puede acceder al mapa interno mediante el cambio de envoltura, el bloqueo no cambiará nada. Simplemente moverá el texto sin ningún beneficio. –

2

¿Esas son todas las funciones que tiene en su envoltorio? Debido a que esta excepción se puede lanzar mientras usted de alguna manera itera sobre la colección en otro lugar. Y supongo que tienes un método sincronizado con una posible condición de carrera obvia, pero probablemente hayas olvidado casos menos obvios. Here la referencia a los documentos de la clase de excepción.

+0

Sí, todos esos métodos están en la clase contenedora. – Cambium

+0

¿Hay algún código en su proyecto que use un iterador para esta clase? ¿Están esos usos sincronizados correctamente? –

+0

Esta clase no tiene iterador. El mapa interno solo se puede modificar a través de los tres métodos que enumeré arriba. – Cambium

0

eso es extraño. No veo la forma en que se lanza la excepción en la ubicación que está identificando (en la implementación predeterminada en Java 6). ¿Obtiene una ConcurrentModificationException en ArrayList o HashMap?¿Ha anulado el método keySet() en su HashMap?

edición mi error - ArrayList obligará al conjunto de claves para iterar (AbstractCollection.toArray() itera sobre sus teclas)

existe en alguna parte del código que es lo que le permite actualizar su mapa interno que no está sincronizado

  • ¿Tiene un método de utilidad que expone su mapa interno que "no debería ser utilizado" o
  • es su mapa interno identificado como ámbito público
+0

No, keySet() no se reemplaza. No creo que esto cause un problema, porque la única manera de acceder a este mapa interno es a través de set(), get() y Rebuild() de la clase contenedora. – Cambium

+0

Estoy completamente de acuerdo con su análisis, sin embargo, tripité/cuadruplicé mi clase, y me aseguré de que esos tres métodos sean los únicos usuarios del mapa interno; y por supuesto, el mapa interno es privado. – Cambium

6

Esta excepción normalmente no tiene nada que ver con sincronización: normalmente se lanza si una Colección se modifica mientras un iterador está iterando a través de ella. Los métodos AddAll pueden usar un iterador, y vale la pena señalar que el bucle foreach elegante también itera sobre las instancias de Iterator.

por ejemplo:

for(Object o : objects) { 
    objects.remove(o); 
} 

es suficiente para conseguir la excepción de algunas colecciones (por ejemplo ArrayList).

James

+1

Hola James, no veo cómo esto es relevante. No creo que esté modificando el mapa/conjunto de claves del mapa mientras lo recorro. – Cambium

+0

@Cambrium: esta es, de hecho, la única forma de que se produzca esta excepción: http://stackoverflow.com/questions/602636/concurrentmodificationexception-and-a-hashmap/602660#602660 – Robin

+0

Es relevante porque se está lanzando la excepción de su código casi con certeza de la manera que describo. – gubby

0

Es internalMap estática? Puede tener varios objetos, cada uno bloqueando this, el objeto, pero sin proporcionar el bloqueo correcto en su InternalMap estática.

+0

Si es así, la solución de Chochos debería funcionar. –

+0

No, no es estático. – Cambium

0

No creo que su set()/get() esté sincronizado con el mismo monitor que rebuild(). Esto hace posible que alguien invoque set/get mientras se ejecuta la línea problemática, específicamente al iterar sobre el conjunto de claves de internalMap durante la llamada addAll() (implementación interna que se expone a través del seguimiento de la pila).

En lugar de hacer set() sincronizados, ¿ha intentado algo en la línea de:

public void set(SomeKey key, SomeValue val) { 
    synchronized(this) { 
     internalMap.put(key, val); // or whatever your get looks like 
    } 
} 

no creo que necesita sincronizar get() en absoluto, pero si insisten:

public SomeValue get(SomeKey key) { 
    synchronized(this) { 
     internalMap.get(key); // or whatever your get looks like 
    } 
} 

De hecho, creo que es mejor sincronizar con internalMap en lugar de this. Tampoco hace daño hacer internalMapvolatile, aunque no creo que esto sea realmente necesario si está seguro de que set()/get()/rebuild() son los únicos métodos que acceden directamente a internalMap, y todos lo están accediendo de forma sincronizada .

private volatile Map internalMap ... 
0

Mientras iteración sobre las teclas, debido a

keysCopy.addAll(internalMap.keySet()); 

se decide que algunas de las entradas se pueden retirar de la LinkedHashMap?

El método removeEldestEntry podría ser verdadero o modificar el mapa, alterando así la iteración.

0

Try declarar el mapa como transitoria

+0

maldita sea, quise decir volátil, como Jack menciona a continuación – Reed

0

intente esto:

public void rebuild(){ 
    /* initialization stuff */ 
    List<SomeKey> keysCopy = new ArrayList<SomeKey>(); 
    synchronized (internalMap) { 
    keysCopy.addAll(internalMap.keySet()); 
    } 
    /* 
    do stuff with keysCopy, update a temporary map 
    */  
    synchronized (internalMap) { 
    internalMap.putAll(tempMap); 
    } 
} 
1

Desde el Javadoc:

Si varios subprocesos tienen acceso a un mapa hash vinculada al mismo tiempo, y por lo menos una de las hilos modifica el mapa estructuralmente, debe estar sincronizado externamente. Esto se logra normalmente sincronizando en algún objeto que naturalmente encapsula el mapa. Si no existe tal objeto, el mapa debe ser "ajustado" utilizando el método Collections.synchronizedMap. Esto se realiza mejor en el momento de la creación, para evitar el acceso accidental no sincronizado al mapa:

Mapa m = Collections.synchronizedMap (new LinkedHashMap (...));

Puede ser más seguro para usted envolver el LinkedHashMap en lugar de solicitar que lo extienda. Por lo tanto, su implementación tendría un miembro de datos internos que es el Mapa devuelto por Collections.synchronizedMap (nuevo LinkedHashMap (...)).

Por favor, consulte el Javadoc Colecciones para más detalles: Collections.synchronizedMap

0

mantener el acceso a internalMap syncronized, de lo contrario se produce debido java.util.ConcurrentModificationException HashMap # modCount (que registra los cambios estructurales) se puede cambiar al mismo tiempo durante la iteración conjunto de claves (. debido keysCopy.addAll (internalMap.keySet() invocación)

Javadoc LinkedHashMap especifica: "En los mapas de hash vinculados acceso ordenado, simplemente consultando el mapa con obtener es una modificación estructural."