A modificação de um Collectiontempo que itera através do Collectionuso de um nãoIterator é permitida pela maioria das Collectionclasses. A biblioteca Java chama uma tentativa de modificar, Collectionenquanto iterando através dela, uma "modificação simultânea". Infelizmente, isso sugere que a única causa possível é a modificação simultânea por vários threads, mas não é assim. Usando apenas um encadeamento, é possível criar um iterador para o Collection(usando Collection.iterator()ou um loop aprimoradofor ), iniciar a iteração (usando Iterator.next()ou entrar de forma equivalente no corpo do forloop aprimorado ), modificar o Collectione continuar a iteração.
Para ajudar os programadores, algumas implementações dessas Collectionclasses tentam detectar modificações simultâneas erradas e lançam um ConcurrentModificationExceptionse detectarem. No entanto, em geral não é possível e prático garantir a detecção de todas as modificações simultâneas. Portanto, o uso incorreto do Collectionnem sempre resulta em um arremesso ConcurrentModificationException.
A documentação do ConcurrentModificationExceptiondiz:
Essa exceção pode ser lançada por métodos que detectaram a modificação simultânea de um objeto quando essa modificação não é permitida ...
Observe que essa exceção nem sempre indica que um objeto foi modificado simultaneamente por um thread diferente. Se um único encadeamento emitir uma sequência de invocações de método que viole o contrato de um objeto, o objeto poderá lançar esta exceção ...
Observe que o comportamento à prova de falhas não pode ser garantido, pois, em geral, é impossível fazer garantias concretas na presença de modificação simultânea não sincronizada. As operações à prova de falhas são realizadas ConcurrentModificationExceptioncom o melhor esforço.
Observe que
A documentação do HashSet, HashMap, TreeSete ArrayListaulas diz o seguinte:
Os iteradores retornados [direta ou indiretamente dessa classe] são rápidos: se a [coleção] for modificada a qualquer momento após a criação do iterador, de qualquer forma, exceto pelo método de remoção do próprio iterador, os Iteratorarremessos a ConcurrentModificationException. Portanto, diante da modificação simultânea, o iterador falha de maneira rápida e limpa, em vez de arriscar um comportamento arbitrário e não determinístico em um tempo indeterminado no futuro.
Observe que o comportamento à prova de falhas de um iterador não pode ser garantido, pois é, em geral, impossível fazer quaisquer garantias concretas na presença de modificação simultânea não sincronizada. Os iteradores à prova de falhas são os ConcurrentModificationExceptionmelhores esforços. Portanto, seria errado escrever um programa que dependesse dessa exceção para sua correção: o comportamento de falha rápida dos iteradores deve ser usado apenas para detectar erros .
Observe novamente que o comportamento "não pode ser garantido" e é apenas "na base do melhor esforço".
A documentação de vários métodos da Mapinterface diz o seguinte:
Implementações não simultâneas devem substituir esse método e, na melhor das hipóteses, lançar um ConcurrentModificationExceptionse for detectado que a função de mapeamento modifica esse mapa durante o cálculo. As implementações simultâneas devem substituir esse método e, na melhor das hipóteses, lançar um IllegalStateExceptionse for detectado que a função de mapeamento modifica esse mapa durante a computação e, como resultado, a computação nunca será concluída.
Observe novamente que apenas uma "base de melhor esforço" é necessária para a detecção e a ConcurrentModificationExceptioné explicitamente sugerida apenas para as classes não simultâneas (sem thread-safe).
Depuração ConcurrentModificationException
Portanto, quando você vê um rastreamento de pilha devido a ConcurrentModificationException, não é possível assumir imediatamente que a causa é o acesso multithread inseguro a um Collection. Você deve examinar o rastreamento de pilha para determinar qual classe Collectionlançou a exceção (um método da classe a lançou direta ou indiretamente) e para qual Collectionobjeto. Então você deve examinar de onde esse objeto pode ser modificado.
- A causa mais comum é a modificação do
Collectiondentro de um forloop aprimorado sobre o Collection. Só porque você não vê um Iteratorobjeto no seu código-fonte, não significa que não Iteratorexista! Felizmente, uma das declarações do forloop defeituoso geralmente estará no rastreamento da pilha; portanto, rastrear o erro geralmente é fácil.
- Um caso mais complicado é quando seu código passa referências ao
Collectionobjeto. Observe que visualizações não modificáveis de coleções (como produzidas por Collections.unmodifiableList()) mantêm uma referência à coleção modificável, portanto, a iteração sobre uma coleção "não modificável" pode gerar a exceção (a modificação foi feita em outro lugar). Outros pontos de vista do seu Collection, como sub-listas , Mapconjuntos de entrada e Mapconjuntos de chaves também reter referências ao original (modificável) Collection. Isso pode ser um problema mesmo para um thread-safe Collection, como CopyOnWriteList; não assuma que coleções (simultâneas) seguras para threads nunca podem lançar a exceção.
- Quais operações podem modificar a
Collectionpodem ser inesperadas em alguns casos. Por exemplo, LinkedHashMap.get()modifica sua coleção .
- Os casos mais difíceis são quando a exceção ocorre devido à modificação simultânea de vários encadeamentos.
Programação para evitar erros de modificação simultâneos
Quando possível, restrinja todas as referências a um Collectionobjeto, para que seja mais fácil evitar modificações simultâneas. Crie Collectionum privateobjeto ou uma variável local e não retorne referências aos Collectionmétodos ou a seus iteradores. É então muito mais fácil examinar todos os lugares onde os itens Collectionpodem ser modificados. Se o Collectiondeve ser usado por vários encadeamentos, é prático garantir que os encadeamentos acessem o Collectionúnico com sincronização e bloqueio apropriados.