A modificação de um Collection
tempo que itera através do Collection
uso de um nãoIterator
é permitida pela maioria das Collection
classes. A biblioteca Java chama uma tentativa de modificar, Collection
enquanto 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 for
loop aprimorado ), modificar o Collection
e continuar a iteração.
Para ajudar os programadores, algumas implementações dessas Collection
classes tentam detectar modificações simultâneas erradas e lançam um ConcurrentModificationException
se 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 Collection
nem sempre resulta em um arremesso ConcurrentModificationException
.
A documentação do ConcurrentModificationException
diz:
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 ConcurrentModificationException
com o melhor esforço.
Observe que
A documentação do HashSet
, HashMap
, TreeSet
e ArrayList
aulas 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 Iterator
arremessos 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 ConcurrentModificationException
melhores 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 Map
interface diz o seguinte:
Implementações não simultâneas devem substituir esse método e, na melhor das hipóteses, lançar um ConcurrentModificationException
se 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 IllegalStateException
se 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 Collection
lançou a exceção (um método da classe a lançou direta ou indiretamente) e para qual Collection
objeto. Então você deve examinar de onde esse objeto pode ser modificado.
- A causa mais comum é a modificação do
Collection
dentro de um for
loop aprimorado sobre o Collection
. Só porque você não vê um Iterator
objeto no seu código-fonte, não significa que não Iterator
exista! Felizmente, uma das declarações do for
loop 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
Collection
objeto. 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 , Map
conjuntos de entrada e Map
conjuntos 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
Collection
podem 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 Collection
objeto, para que seja mais fácil evitar modificações simultâneas. Crie Collection
um private
objeto ou uma variável local e não retorne referências aos Collection
métodos ou a seus iteradores. É então muito mais fácil examinar todos os lugares onde os itens Collection
podem ser modificados. Se o Collection
deve ser usado por vários encadeamentos, é prático garantir que os encadeamentos acessem o Collection
único com sincronização e bloqueio apropriados.