ConcurrentModificationException apesar de usar sincronizado

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

apesar da declaração sincronizada no header da declaração, eu ainda recebo uma exceção ConcurrentModificationException na linha onde eu uso iterator.next (); o que está errado aqui?

ConcurrentModificationException geralmente não tem nada a ver com vários threads. Na maior parte do tempo, ocorre porque você está modificando a coleção sobre a qual está interagindo dentro do corpo do loop de iteração. Por exemplo, isso causará isso:

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

Nesse caso, você deve usar o método iterator.remove() . Isso ocorre igualmente se você estiver adicionando à coleção, caso em que não há solução geral. No entanto, o subtipo ListIterator pode ser usado se estiver lidando com uma lista e isso tiver um método add() .

Eu concordo com as declarações acima sobre ConcurrentModificationException muitas vezes acontecendo como resultado da modificação da coleção no mesmo thread de iteração. No entanto, nem sempre é o motivo.

A coisa a lembrar sobre o synchronized é que ele só garante access exclusivo se todos que acessam o recurso compartilhado também sincronizarem.

Por exemplo, você pode sincronizar o access a uma variável compartilhada:

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

E você pode pensar que você tem access exclusivo a ele. No entanto, não há nada para impedir que outro thread faça algo sem o bloco synchronized :

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

Através da má sorte (ou da Lei de Murphy , “Qualquer coisa que possa dar errado, vai dar errado”), esses dois segmentos podem acontecer ao mesmo tempo. No caso de modificações estruturais de algumas collections amplamente utilizadas (por exemplo, ArrayList , HashSet , HashMap , etc), isso pode resultar em um ConcurrentModificationException .

É difícil evitar completamente o problema:

  • Você pode documentar os requisitos de synchronization, por exemplo, inserindo “você deve sincronizar em blah antes de modificar esta coleção” ou “adquirir bloo lock primeiro”, mas isso depende dos usuários descobrirem, lerem, entenderem e aplicarem a instrução.

    Há a anotação javax.annotation.concurrent.GuardedBy , que pode ajudar a documentar isso de maneira padronizada; O problema é que você tem que ter alguns meios de verificar o uso correto da anotação no conjunto de ferramentas. Por exemplo, você pode usar algo como o errorprone do Google , que pode ser verificado em algumas situações, mas não é perfeito .

  • Para operações simples em collections, você pode fazer uso dos methods de fábrica Collections.synchronizedXXX , que envolvem uma coleção para que cada chamada de método seja sincronizada primeiro na coleção subjacente, por exemplo, o método SynchronizedCollection.add :

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

    Onde mutex é a instância sincronizada (geralmente a própria SynchronizedCollection ) e c é a coleção agrupada.

    As duas ressalvas com essa abordagem são:

    1. É preciso ter cuidado para que a coleção envolvida não possa ser acessada de qualquer outra forma, pois isso permitiria o access não sincronizado, o problema original. Isso geralmente é obtido agrupando a coleção imediatamente na construção:

       Collections.synchronizedList(new ArrayList()); 
    2. A synchronization é aplicada por chamada de método, portanto, se você estiver fazendo alguma operação composta, por exemplo

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

      então você não tem access exclusivo durante toda a operação, apenas para as chamadas size() e add(...) individualmente.

      Para obter access mutuamente exclusivo para a duração da operação composta, você precisaria sincronizar externamente, por exemplo, synchronized (c) { ... } . Isso requer que você saiba a coisa correta para sincronizar, no entanto, que pode ou não ser c .