164 votes

Est-il possible d'obtenir des valeurs d'une table de hachage java.util.HashMap à partir de plusieurs threads (sans modification) ?

Il arrive qu'une carte soit construite et qu'une fois initialisée, elle ne soit plus jamais modifiée. Elle sera cependant accessible (via get(key) uniquement) à partir de plusieurs threads. Est-il sûr d'utiliser un java.util.HashMap de cette manière ?

(Actuellement, j'utilise volontiers un java.util.concurrent.ConcurrentHashMap Je n'ai pas de besoin mesuré d'améliorer la performance, mais je suis simplement curieux de savoir si un simple HashMap suffirait. Cette question est donc pas "Il ne s'agit pas non plus d'une question de performance. La question est plutôt : "Est-ce que ce serait sûr ?")

4 votes

De nombreuses réponses sont correctes en ce qui concerne l'exclusion mutuelle des threads en cours d'exécution, mais incorrectes en ce qui concerne les mises à jour de la mémoire. J'ai voté en conséquence, mais il y a encore beaucoup de réponses incorrectes avec des votes positifs.

0 votes

@Heath Borders, si l'instance a était une HashMap non modifiable initialisée statiquement, elle devrait être sûre pour une lecture concurrente (puisque les autres threads n'auraient pas pu manquer les mises à jour puisqu'il n'y avait pas de mises à jour), n'est-ce pas ?

0 votes

S'il est initialisé de manière statique et n'est jamais modifié en dehors du bloc statique, alors cela peut être correct car toute l'initialisation statique est synchronisée par la fonction ClassLoader . Cela mérite une question à part entière. Je continuerais à le synchroniser explicitement et à le profiler pour vérifier qu'il cause de réels problèmes de performance.

74voto

Taylor Gautier Points 2044

Jeremy Manson, le dieu du modèle de mémoire Java, a publié un blog en trois parties sur ce sujet. En effet, vous posez essentiellement la question suivante : "Est-il possible d'accéder en toute sécurité à une table de hachage immuable ? Mais vous devez répondre au prédicat de cette question qui est : "Mon HashMap est-il immuable ? La réponse pourrait vous surprendre : Java dispose d'un ensemble de règles relativement complexes pour déterminer l'immutabilité.

Pour plus d'informations sur le sujet, lisez les articles de blog de Jeremy :

Première partie sur l'immutabilité en Java : http://jeremymanson.blogspot.com/2008/04/immutability-in-java.html

Partie 2 sur l'immutabilité en Java : http://jeremymanson.blogspot.com/2008/07/immutability-in-java-part-2.html

Partie 3 sur l'immutabilité en Java : http://jeremymanson.blogspot.com/2008/07/immutability-in-java-part-3.html

3 votes

C'est un bon point, mais je m'appuie sur une initialisation statique, pendant laquelle aucune référence ne s'échappe, donc cela devrait être sûr.

10 votes

Je ne vois pas en quoi il s'agit d'une réponse bien notée (ou même d'une réponse). D'une part, il n'y a même pas répondre La question ne mentionne pas le principe clé qui déterminera si le produit est sûr ou non : publication sécurisée . La "réponse" se résume à "c'est astucieux" et voici trois liens (complexes) que vous pouvez lire.

0 votes

Il répond à la question à la toute fin de la première phrase. En termes de réponse, il soulève le fait que l'immutabilité (à laquelle il est fait allusion dans le premier paragraphe de la question) n'est pas simple, ainsi que des ressources précieuses qui expliquent ce sujet plus en détail. Les points ne mesurent pas s'il s'agit d'une réponse, mais si la réponse a été "utile" aux autres. Le fait que la réponse soit acceptée signifie qu'il s'agit de la réponse que le PO recherchait et que votre réponse a été acceptée.

38voto

Heath Borders Points 8067

Les lectures sont sûres du point de vue de la synchronisation, mais pas du point de vue de la mémoire. C'est un point qui est largement mal compris par les développeurs Java, y compris ici sur Stackoverflow. (Observez l'évaluation de cette réponse pour preuve).

Si d'autres threads sont en cours d'exécution, il se peut qu'ils ne voient pas une copie mise à jour de la table de hachage si aucune écriture mémoire n'est effectuée par le thread en cours. Les écritures en mémoire se produisent par l'utilisation des mots-clés synchronized ou volatile, ou par l'utilisation de certaines constructions de concurrence Java.

Voir Article de Brian Goetz sur le nouveau modèle de mémoire Java pour plus de détails.

0 votes

Désolé pour la double soumission Heath, je n'ai remarqué la vôtre qu'après avoir soumis la mienne :)

2 votes

Je suis heureux qu'il y ait d'autres personnes ici qui comprennent réellement les effets de la mémoire.

1 votes

En effet, bien qu'aucun thread ne verra l'objet avant qu'il ne soit initialisé correctement, je ne pense pas que ce soit un problème dans ce cas.

11voto

Dave L. Points 19623

Après quelques recherches supplémentaires, j'ai trouvé ceci dans la rubrique doc java (souligné par moi) :

Notez que cette mise en œuvre n'est pas synchronisée. I accèdent simultanément à une table de hachage, et qu'au moins au moins un des threads modifie la structure de la structurellement la carte, celle-ci doit être synchronisée de manière externe. ( est toute opération qui ajoute ou supprime ajoute ou supprime un ou plusieurs mappings ; le simple changement de la valeur associée à une à une clé qu'une instance contient déjà n'est pas une modification structurelle).

Cela semble impliquer qu'il sera sûr, en supposant que l'inverse de l'énoncé soit vrai.

1 votes

Bien qu'il s'agisse d'un excellent conseil, comme l'indiquent d'autres réponses, il existe une réponse plus nuancée dans le cas d'une instance de carte immuable et publiée en toute sécurité. Mais vous ne devriez le faire que si vous savez ce que vous faites.

1 votes

J'espère que des questions comme celles-ci permettront à un plus grand nombre d'entre nous de savoir ce qu'ils font.

0 votes

Ce n'est pas vraiment correct. Comme l'indiquent les autres réponses, il doit y avoir un se produit avant entre la dernière modification et toutes les lectures ultérieures "thread safe". Normalement, cela signifie que vous devez publier l'objet en toute sécurité après il a été créé et ses modifications sont effectuées. Voir la première réponse correcte.

8voto

Alex Miller Points 28225

Il est à noter que dans certaines circonstances, un get() à partir d'une HashMap non synchronisée peut provoquer une boucle infinie. Cela peut se produire si un put() concurrent provoque un rehash de la Map.

http://lightbody.net/blog/2005/07/hashmapget_can_cause_an_infini.html

1 votes

En fait, j'ai vu cela bloquer la JVM sans consommer de CPU (ce qui est peut-être pire).

2 votes

I penser ce code a été réécrit de telle sorte qu'il n'est plus possible d'obtenir une boucle infinie. Mais vous ne devriez toujours pas utiliser les fonctions get et put d'une HashMap non synchronisée pour d'autres raisons.

0 votes

@AlexMiller même en dehors des autres raisons (je suppose que vous faites référence à la publication sécurisée), je ne pense pas qu'un changement d'implémentation devrait être une raison pour assouplir les restrictions d'accès, à moins que cela ne soit explicitement autorisé par la documentation. En l'occurrence, le HashMap Javadoc pour Java 8 contient toujours cet avertissement : Note that this implementation is not synchronized. If multiple threads access a hash map concurrently, and at least one of the threads modifies the map structurally, it must be synchronized externally.

7voto

Alexander Points 4298

Il y a cependant un élément important. Il est sûr d'accéder à la carte, mais en général il n'est pas garanti que tous les threads verront exactement le même état (et donc les mêmes valeurs) de la HashMap. Cela peut se produire sur les systèmes multiprocesseurs où les modifications apportées à la table de hachage par un thread (par exemple, celui qui l'a remplie) peuvent rester dans le cache de ce processeur et ne seront pas vues par les threads s'exécutant sur d'autres processeurs, jusqu'à ce qu'une opération de clôture de la mémoire soit effectuée pour assurer la cohérence du cache. La spécification du langage Java est explicite à ce sujet : la solution consiste à acquérir un verrou (synchronisé (...)) qui déclenche une opération de clôture de la mémoire. Ainsi, si vous êtes sûr qu'après avoir rempli la table de hachage, chacun des threads acquiert TOUT verrou, il est possible d'accéder à la table de hachage à partir de n'importe quel thread jusqu'à ce que la table de hachage soit à nouveau modifiée.

0 votes

Je ne suis pas sûr que le thread qui y accède va acquérir un verrou, mais je suis sûr qu'il n'obtiendra pas de référence à l'objet avant qu'il ait été initialisé, donc je ne pense pas qu'il puisse avoir une copie périmée.

0 votes

@Alex : La référence au HashMap peut être volatile pour créer les mêmes garanties de visibilité de la mémoire. @Dave : Il est Il est possible de voir des références à de nouveaux objets avant que le travail de son ctor ne devienne visible pour votre thread.

0 votes

@Christian Dans le cas général, certainement. Je disais que dans ce code, ce n'est pas le cas.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X