web-dev-qa-db-fra.com

Un thread HashMap est-il sûr pour différentes clés?

Si j'ai deux threads multiples accédant à un HashMap, mais que je garantis qu'ils n'accéderont jamais à la même clé en même temps, cela pourrait-il toujours conduire à une condition de concurrence?

74
hsribei

Dans la réponse de @ dotsid, il dit ceci:

Si vous modifiez un HashMap de quelque façon que ce soit, votre code est simplement cassé.

Il a raison. Un HashMap mis à jour sans synchronisation se cassera même si les threads utilisent des jeux de clés disjoints. Voici certaines des choses qui peuvent mal tourner.

  • Si un thread fait un put, alors un autre thread peut voir une valeur périmée pour la taille de la table de hachage.

  • Lorsqu'un thread effectue un put qui déclenche une reconstruction de la table, un autre thread peut voir des versions transitoires ou périmées de la référence de tableau de hachage, sa taille, son contenu ou les chaînes de hachage. Le chaos peut s'ensuivre.

  • Lorsqu'un thread fait un put pour une clé qui entre en collision avec une clé utilisée par un autre thread, et que ce dernier thread fait un put pour sa clé, alors ce dernier peut voir une copie périmée de référence de la chaîne de hachage. Le chaos peut s'ensuivre.

  • Lorsqu'un thread sonde la table avec une clé qui entre en collision avec l'une des clés d'un autre thread, il peut rencontrer cette clé sur la chaîne. Il appellera des égaux sur cette clé, et si les threads ne sont pas synchronisés, la méthode des égaux peut rencontrer un état périmé dans cette clé.

Et si deux threads effectuent simultanément des requêtes put ou remove, les conditions de concurrence sont nombreuses.

Je peux penser à trois solutions:

  1. Utilisez un ConcurrentHashMap.
  2. Utilisez un HashMap régulier mais synchronisez à l'extérieur; par exemple. en utilisant des mutex primitifs, des objets Lock, etc.
  3. Utilisez un HashMap différent pour chaque thread. Si les threads ont vraiment un ensemble de clés disjointes, il ne devrait pas être nécessaire (d'un point de vue algorithmique) de partager une seule carte. En effet, si vos algorithmes impliquent les threads itérant les clés, les valeurs ou les entrées de la carte à un moment donné, la division de la carte unique en plusieurs cartes pourrait donner une accélération significative pour cette partie du traitement.
85
Stephen C

Utilisez simplement un ConcurrentHashMap. Le ConcurrentHashMap utilise plusieurs verrous qui couvrent une gamme de compartiments de hachage pour réduire les risques de contestation d'un verrou. L'acquisition d'un verrou incontesté a un impact marginal sur les performances.

Pour répondre à votre question initiale: Selon le javadoc, tant que la structure de la carte ne change pas, vous êtes bien. Cela signifie pas de suppression d'éléments du tout et pas d'ajout de nouvelles clés qui ne sont pas déjà dans la carte. Remplacer la valeur associée aux clés existantes est correct.

Si plusieurs threads accèdent simultanément à une mappe de hachage et qu'au moins l'un des threads modifie la mappe structurellement, elle doit être synchronisée en externe. (Une modification structurelle est toute opération qui ajoute ou supprime un ou plusieurs mappages; la simple modification de la valeur associée à une clé qu'une instance contient déjà n'est pas une modification structurelle.)

Bien qu'il ne garantisse pas la visibilité. Vous devez donc être prêt à accepter de récupérer occasionnellement des associations périmées.

26
Tim Bender

Cela dépend de ce que vous entendez par "accès". Si vous venez de lire, vous pouvez lire même les mêmes clés tant que la visibilité des données est garantie selon les règles " arrive-avant ". Cela signifie que HashMap ne doit pas changer et que toutes les modifications (constructions initiales) doivent être terminées avant qu'un lecteur ne commence à accéder à HashMap.

Si vous modifiez un HashMap de quelque façon que ce soit, votre code est simplement cassé. @Stephen C explique très bien pourquoi.

EDIT: Si le premier cas est votre situation réelle, je vous recommande d'utiliser Collections.unmodifiableMap() pour être sûr que votre HashMap n'est jamais modifié. Les objets pointés par HashMap ne devraient pas changer également, donc l'utilisation agressive du mot clé final peut vous aider.

Et comme le dit @Lars Andren, ConcurrentHashMap est le meilleur choix dans la plupart des cas.

5
Denis Bazhenov

La modification d'un HashMap sans synchronisation appropriée à partir de deux threads peut facilement conduire à une condition de concurrence.

  • Lorsqu'une put() conduit à un redimensionnement de la table interne, cela prend un certain temps et l'autre thread continue à écrire dans l'ancienne table.
  • Deux put() pour des clés différentes conduisent à une mise à jour du même compartiment si les codes de hachage des clés sont égaux à la taille de la table. (En fait, la relation entre le code de hachage et l'index de compartiment est plus compliquée, mais des collisions peuvent toujours se produire.)
3
Christian Semrau