Pergunta

Em um aplicativo multithread em que estou trabalhando, ocasionalmente vemos ConcurrentModificationExceptions em nossas listas (que são principalmente ArrayList, às vezes vetores).Mas há outros momentos em que penso que modificações simultâneas estão acontecendo porque parece haver itens faltando na iteração da coleção, mas nenhuma exceção é lançada.Eu sei que os documentos para ConcurrentModificationException diz que você não pode confiar nisso, mas como posso garantir que não estou modificando uma lista simultaneamente?E agrupar todos os acessos à coleção em um bloco sincronizado é a única maneira de evitá-lo?

Atualizar: Sim, eu sei sobre Collections.synchronizedCollection, mas não protege contra alguém modificando a coleção enquanto você a itera.Acho que pelo menos parte do meu problema está acontecendo quando alguém adiciona algo a uma coleção enquanto estou interagindo com ela.

Segunda atualização Se alguém quiser combinar a menção desyncedCollection e clonagem como Jason fez com uma menção de java.util.concurrent e as estruturas de coleções Apache como jacekfoo e Javamann fizeram, posso aceitar uma resposta.

Foi útil?

Solução

Sua pergunta original parece solicitar um iterador que veja atualizações ao vivo na coleção subjacente, permanecendo seguro para threads.Este é um problema incrivelmente caro para resolver no caso geral, e é por isso que nenhuma das classes de coleção padrão o faz.

Existem muitas maneiras de obter soluções parciais para o problema e, na sua aplicação, uma delas pode ser suficiente.

Jasão dá uma maneira específica de obter segurança de thread e evitar lançar uma ConcurrentModificationException, mas apenas às custas da vivacidade.

Javamann menciona duas aulas específicas no java.util.concurrent pacote que resolve o mesmo problema de forma livre de bloqueios, onde a escalabilidade é crítica.Eles foram fornecidos apenas com o Java 5, mas houve vários projetos que portam a funcionalidade do pacote para versões anteriores do Java, incluindo Este, embora não tenham um desempenho tão bom em JREs anteriores.

Se você já estiver usando algumas das bibliotecas Apache Commons, então como jacekfoo aponta, o estrutura de coleções apache contém algumas classes úteis.

Você também pode considerar olhar para o Estrutura de coleções do Google.

Outras dicas

Dependendo da sua frequência de atualização, um dos meus favoritos é CopyOnWriteArrayList ou CopyOnWriteArraySet.Eles criam uma nova lista/conjunto de atualizações para evitar exceções de modificação simultânea.

Confira java.util.concurrent para versões das classes Collections padrão que são projetadas para lidar melhor com a simultaneidade.

Sim, você precisa sincronizar o acesso aos objetos das coleções.

Alternativamente, você pode usar os wrappers sincronizados em torno de qualquer objeto existente.Ver Coleções.synchronizedCollection().Por exemplo:

List<String> safeList = Collections.synchronizedList( originalList );

Porém todo código precisa usar a versão segura, e mesmo assim iterar enquanto outro thread é modificado resultará em problemas.

Para resolver o problema de iteração, copie a lista primeiro.Exemplo:

for ( String el : safeList.clone() )
{ ... }

Para coleções mais otimizadas e seguras para threads, consulte também java.util.concurrent.

Normalmente você recebe uma ConcurrentModificationException se estiver tentando remover um elemento de uma lista enquanto ele está sendo iterado.

A maneira mais fácil de testar isso é:

List<Blah> list = new ArrayList<Blah>();
for (Blah blah : list) {
     list.remove(blah); // will throw the exception
}

Não tenho certeza de como você contornaria isso.Talvez você precise implementar sua própria lista thread-safe ou criar cópias da lista original para gravação e ter uma classe sincronizada que grave na lista.

Você poderia tentar usar cópia defensiva para que as modificações em um List não afete os outros.

Agrupar os acessos à coleção em um bloco sincronizado é a maneira correta de fazer isso.A prática de programação padrão determina o uso de algum tipo de mecanismo de bloqueio (semáforo, mutex, etc.) ao lidar com estado que é compartilhado por vários threads.

Dependendo do seu caso de uso, no entanto, geralmente você pode fazer algumas otimizações para bloquear apenas em determinados casos.Por exemplo, se você tiver uma coleção que é lida com frequência, mas raramente escrita, poderá permitir leituras simultâneas, mas impor um bloqueio sempre que uma gravação estiver em andamento.Leituras simultâneas só causam conflitos se a coleção estiver em processo de modificação.

ConcurrentModificationException é o melhor esforço porque o que você está perguntando é um problema difícil.Não há uma boa maneira de fazer isso de maneira confiável sem sacrificar o desempenho, além de provar que seus padrões de acesso não modificam a lista simultaneamente.

A sincronização provavelmente impediria modificações simultâneas e pode ser o que você recorre no final, mas pode acabar custando caro.A melhor coisa a fazer é provavelmente sentar e pensar um pouco sobre seu algoritmo.Se você não conseguir encontrar uma solução sem bloqueios, recorra à sincronização.

Veja a implementação.Basicamente armazena um int:

transient volatile int modCount;

e isso é incrementado quando há uma 'modificação estrutural' (como remoção).Se o iterador detectar que modCount foi alterado, ele lançará uma exceção de modificação simultânea.

Sincronizar (via Collections.synchronizedXXX) não será bom, pois não garante a segurança do iterador, apenas sincroniza gravações e leituras via put, get, set ...

Consulte java.util.concurennt e estrutura de coleções Apache (ele possui algumas classes que são otimizadas para funcionar corretamente em ambiente simultâneo quando há mais leituras (que não são sincronizadas) do que gravações - consulte FastHashMap.

Você também pode sincronizar iterações na lista.

List<String> safeList = Collections.synchronizedList( originalList );

public void doSomething() {
   synchronized(safeList){
     for(String s : safeList){
           System.out.println(s);

     }
   }

}

Isso bloqueará a lista na sincronização e bloqueará todos os threads que tentarem acessar a lista enquanto você a edita ou itera sobre ela.A desvantagem é que você cria um gargalo.

Isso economiza um pouco de memória no método .clone() e pode ser mais rápido dependendo do que você está fazendo na iteração...

Coleções.synchronizedList() renderizará uma lista nominalmente thread-safe e java.util.concurrent possui recursos mais poderosos.

Isso eliminará sua exceção de modificação simultânea.Não vou falar sobre a eficiência;)

List<Blah> list = fillMyList();
List<Blah> temp = new ArrayList<Blah>();
for (Blah blah : list) {
     //list.remove(blah);  would throw the exception
     temp.add(blah);
}
list.removeAll(temp);
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top