ConcurrentModificationException a pesar de usar sincronización

public synchronized X getAnotherX(){ if(iterator.hasNext()){ X b = iterator.next(); String name = b.getInputFileName(); ... return b; } else{return null;} } 

a pesar de la statement sincronizada en el encabezado de statement, aún obtengo una excepción ConcurrentModificationException en la línea donde uso iterator.next (); ¿Qué está mal aquí?

ConcurrentModificationException generalmente no tiene nada que ver con múltiples hilos. La mayoría de las veces ocurre porque estás modificando la colección sobre la que se está iterando dentro del cuerpo del ciclo de iteración. Por ejemplo, esto lo causará:

 Iterator iterator = collection.iterator(); while (iterator.hasNext()) { Item item = (Item) iterator.next(); if (item.satisfiesCondition()) { collection.remove(item); } } 

En este caso, debe usar el método iterator.remove() lugar. Esto ocurre igualmente si está agregando a la colección, en cuyo caso no hay una solución general. Sin embargo, el subtipo ListIterator se puede usar si se trata de una lista y tiene un método add() .

Estoy de acuerdo con las afirmaciones anteriores sobre ConcurrentModificationException menudo ocurren como resultado de modificar la colección en el mismo hilo que la iteración. Sin embargo, no siempre es la razón.

Lo que hay que recordar sobre synchronized es que solo garantiza el acceso exclusivo si todos los que acceden al recurso compartido también se sincronizan.

Por ejemplo, puede sincronizar el acceso a una variable compartida:

 synchronized (foo) { foo.setBar(); } 

Y puede pensar que tiene acceso exclusivo a él. Sin embargo, no hay nada para detener otro hilo simplemente haciendo algo sin el bloque synchronized :

 foo.setBar(); // No synchronization first. 

A través de la mala suerte (o la Ley de Murphy , “Todo lo que puede salir mal, saldrá mal”), estos dos hilos pueden ejecutarse al mismo tiempo. En el caso de modificaciones estructurales de algunas colecciones ampliamente utilizadas (por ejemplo, ArrayList , HashSet , HashMap , etc.), esto puede dar como resultado una ConcurrentModificationException .

Es difícil evitar el problema por completo:

  • Puede documentar los requisitos de sincronización, por ejemplo, insertando “debe sincronizarse en blah antes de modificar esta colección” o “adquirir bloo lock primero”, pero eso depende de que los usuarios descubran, lean, entiendan y apliquen la instrucción.

    Existe la anotación javax.annotation.concurrent.GuardedBy , que puede ayudar a documentar esto de forma estandarizada; el problema es que debes tener algún medio para verificar el uso correcto de la anotación en la cadena de herramientas. Por ejemplo, es posible que pueda usar algo como errorprone de Google , que puede verificar en algunas situaciones, pero no es perfecto .

  • Para operaciones simples en colecciones, puede hacer uso de los métodos de fábrica Collections.synchronizedXXX , que envuelven una colección para que cada llamada al método se sincronice primero en la colección subyacente, por ejemplo, el método SynchronizedCollection.add :

     @Override public boolean add(E e) { synchronized (mutex) { return c.add(obj); } } 

    Donde mutex es la instancia sincronizada (a menudo, la SynchronizedCollection ) y c es la colección envuelta.

    Las dos advertencias con este enfoque son:

    1. Debe tener cuidado de que no se pueda acceder a la colección empaquetada de ninguna otra forma, ya que eso permitiría el acceso no sincronizado, el problema original. Esto se logra típicamente envolviendo la colección inmediatamente en la construcción:

       Collections.synchronizedList(new ArrayList()); 
    2. La sincronización se aplica por llamada de método, por lo que si está realizando alguna operación compuesta, por ejemplo

       if (c.size() > 5) { c.add(new Frob()); } 

      entonces no tiene acceso exclusivo durante esa operación, solo para el size() y add(...) llamadas individualmente.

      Para obtener acceso mutuamente exclusivo durante la operación compuesta, necesitaría sincronizar externamente, por ejemplo synchronized (c) { ... } . Sin embargo, esto requiere que sepas en qué sincronizar, que puede o no ser c .