Se eu tiver dois threads múltiplos acessando um HashMap, mas garantir que eles nunca acessem a mesma chave ao mesmo tempo, isso ainda pode levar a uma condição de corrida?
fonte
Se eu tiver dois threads múltiplos acessando um HashMap, mas garantir que eles nunca acessem a mesma chave ao mesmo tempo, isso ainda pode levar a uma condição de corrida?
Na resposta de @ dotsid, ele diz o seguinte:
Se você alterar um HashMap de qualquer forma, seu código será simplesmente quebrado.
Ele está correto. Um HashMap atualizado sem sincronização será interrompido, mesmo se os threads estiverem usando conjuntos de chaves separados. Aqui estão algumas das coisas que podem dar errado.
Se um thread faz um put
, então outro thread pode ver um valor obsoleto para o tamanho do hashmap.
Quando um thread faz um put
que dispara uma reconstrução da tabela, outro thread pode ver versões temporárias ou obsoletas da referência de matriz de hashtable, seu tamanho, seu conteúdo ou as cadeias de hash. O caos pode acontecer.
Quando um encadeamento faz um put
para uma chave que colide com alguma chave usada por algum outro encadeamento e o último encadeamento faz um put
para sua chave, o último pode ver uma cópia desatualizada da referência da cadeia hash. O caos pode acontecer.
Quando um thread investiga a mesa com uma chave que colide com uma das chaves de outro thread, ele pode encontrar essa chave na cadeia. Ele chamará equals nessa chave e, se os threads não estiverem sincronizados, o método equals pode encontrar um estado obsoleto nessa chave.
E se você tem dois tópicos simultaneamente fazendo put
ou remove
pedidos, existem inúmeras oportunidades para condições de corrida.
Posso pensar em três soluções:
ConcurrentHashMap
.HashMap
mas sincronize do lado de fora; por exemplo, usando mutexes primitivos, Lock
objetos, etc.HashMap
para cada tópico. Se os encadeamentos realmente tiverem um conjunto de chaves separado, não haverá necessidade (de uma perspectiva algorítmica) de compartilhar um único Mapa. Na verdade, se seus algoritmos envolvem os threads que iteram as chaves, valores ou entradas do mapa em algum ponto, a divisão de um único mapa em vários mapas pode fornecer um aumento significativo de velocidade para essa parte do processamento.Basta usar um ConcurrentHashMap. O ConcurrentHashMap usa vários bloqueios que abrangem uma variedade de depósitos de hash para reduzir as chances de um bloqueio ser contestado. Há um impacto marginal de desempenho na aquisição de um bloqueio não contestado.
Para responder à sua pergunta original: De acordo com o javadoc, contanto que a estrutura do mapa não mude, você está bem. Isso significa nenhuma remoção de elementos e nenhuma adição de novas chaves que ainda não estão no mapa. Substituir o valor associado às chaves existentes é adequado.
Se vários threads acessam um mapa hash simultaneamente e pelo menos um dos threads modifica o mapa estruturalmente, ele deve ser sincronizado externamente. (Uma modificação estrutural é qualquer operação que adiciona ou exclui um ou mais mapeamentos; simplesmente alterar o valor associado a uma chave que uma instância já contém não é uma modificação estrutural.)
Embora não dê nenhuma garantia sobre a visibilidade. Portanto, você deve estar disposto a aceitar a recuperação de associações obsoletas ocasionalmente.
Depende do que você quer dizer com "acessar". Se você apenas estiver lendo, poderá ler até mesmo as mesmas chaves, desde que a visibilidade dos dados seja garantida pelas regras " acontece antes ". Isso significa que HashMap
não deve mudar e todas as alterações (construções iniciais) devem ser concluídas antes que qualquer leitor comece a acessar HashMap
.
Se você alterar um HashMap
de qualquer forma, seu código será simplesmente quebrado. @Stephen C fornece uma boa explicação do porquê.
EDITAR: Se o primeiro caso for sua situação real, eu recomendo que você use Collections.unmodifiableMap()
para ter certeza de que seu HashMap nunca é alterado. Objetos que são apontados por HashMap
não devem mudar também, então o uso agressivo de final
palavras-chave pode ajudá-lo.
E, como @Lars Andren diz, ConcurrentHashMap
é a melhor escolha na maioria dos casos.
unmodifiableMap
garante que o cliente não pode alterar o mapa. Não faz nada para garantir que o mapa subjacente não seja alterado.Modificar um HashMap sem a sincronização adequada de dois threads pode facilmente levar a uma condição de corrida.
put()
leva a um redimensionamento da tabela interna, isso leva algum tempo e o outro thread continua a gravar na tabela antiga.put()
para chaves diferentes levam a uma atualização do mesmo intervalo se os hashcodes das chaves forem iguais ao módulo do tamanho da tabela. (Na verdade, a relação entre o hashcode e o índice do bucket é mais complicada, mas ainda podem ocorrer colisões.)fonte
HashMap
implementação que você está usando, você pode obter a corrupção dasHashMap
estruturas de dados, etc., causada por anomalias de memória.