Est-il sûr d'obtenir les valeurs de java.util.Table de hachage à partir de plusieurs threads (pas de modification)?
Il y a un cas où une carte sera construit, et une fois qu'il est initialisé, il ne sera jamais à nouveau modifié. Il sera toutefois accessible (via get(clé) uniquement) à partir de plusieurs threads. Est-il sécuritaire d'utiliser un java.util.HashMap
de cette façon?
(Actuellement, je suis heureux à l'aide d'un java.util.concurrent.ConcurrentHashMap
, et n'ont pas mesuré la nécessité d'améliorer la performance, mais je suis simplement curieux de savoir si un simple HashMap
suffirait. Par conséquent, cette question est pas "Qui dois-je utiliser?" elle n'est pas une question de la performance. La question est plutôt "Serait-il en sécurité?")
- Beaucoup de réponses ici sont correctes au sujet de l'exclusion mutuelle de threads en cours d'exécution, mais incorrectes concernant la mémoire des mises à jour. J'ai voté en haut/en bas en conséquence, mais il ya encore beaucoup de réponses incorrectes avec des votes positifs.
- Les frontières, si l'instance a été initialisé de manière statique inmodifiable table de hachage, il devrait être à l'abri des troubles de la lecture (comme les autres threads ne pouvais pas manquer les mises à jour car il n'y avait pas de mises à jour), droit?
- Si c'est de manière statique initialisé et jamais modifiée en dehors de la statique bloc, alors il pourrait être ok parce que tous initialisation statique est synchronisé par l'
ClassLoader
. Que vaut une question distincte sur son propre. J'avais toujours explicitement de le synchroniser et de profil pour vérifier qu'il était la cause réelle des problèmes de performances. - qu'entendez-vous par "la mémoire des mises à jour"? La JVM est un modèle formel qui définit des choses comme la visibilité, l'atomicité, passe-avant le les relations, mais ne pas utiliser des termes comme "la mémoire des mises à jour". Vous devez préciser, de préférence en utilisant la terminologie du JLS.
- Je suppose que vous n'êtes pas encore à la recherche de la réponse après 8 ans, mais pour l'enregistrement, la clé de la confusion dans presque toutes les réponses, c'est qu'ils l'accent sur les mesures que vous prenez sur le plan de l'objet. Vous ai déjà expliqué que vous ne modifiez jamais l'objet, donc, c'est tout hors de propos. La seule "gotcha", puis est comment vous publier la référence pour la
Map
, que vous n'avez pas à l'expliquer. Si vous n'avez pas le faire en toute sécurité, il n'est pas sûr. Si vous le faites en toute sécurité, il est. Les détails dans ma réponse.
Vous devez vous connecter pour publier un commentaire.
Votre idiome est sûr si et seulement si la référence à la
HashMap
est en toute sécurité publié. Plutôt que quoi que ce soit concernant le fonctionnement interne deHashMap
lui-même, sûr publication traite de la façon dont la construction de thread rend la référence à la carte visible pour les autres threads.En gros, la seule possible, la course est ici entre la construction de la
HashMap
et toute la lecture de threads qui peuvent y accéder avant il est entièrement construit. La plupart de la discussion à propos de ce qui se passe à l'état de l'objet map, mais cela est sans importance puisque vous ne modifiez jamais - donc la seule partie intéressante est de savoir comment leHashMap
de référence est publié.Par exemple, imaginez que vous publier la carte comme ceci:
... et à un certain point
setMap()
est appelée avec une carte, et d'autres threads à l'aide deSomeClass.MAP
pour accéder à la carte, et de vérifier la valeur null comme ceci:C'est pas sûr même si elle apparaît probablement comme si il est. Le problème est qu'il n'y a pas de -passe-avant relation entre l'ensemble des
SomeObject.MAP
et la lecture ultérieure sur un autre thread, le thread de lecture est libre de voir une partie de construit la carte. Cela peut très bien faire rien et même dans la pratique, il fait des choses comme mettre le thread de lecture dans une boucle infinie.En toute sécurité de publier la carte, vous devez établir un se passe-avant relation entre la l'écriture de la référence à la
HashMap
(c'est à dire, la publication) les lecteurs de référence (c'est à dire, de la consommation). Idéalement, il y a seulement quelques, faciles à mémoriser, des moyens de accomplir que[1]:Les plus intéressantes pour votre scénario (2), (3) et (4). En particulier, (3) s'applique directement sur le code que j'ai ci-dessus: si vous transformez la déclaration de
MAP
à:puis tout est casher: les lecteurs qui voient un non-null valeur nécessairement un se passe-avant relation avec le magasin de
MAP
et donc de voir tous les magasins associés à la génération de la carte.L'autre modification des méthodes de la sémantique de votre méthode, puisque les deux (2) (à l'aide de la statique initalizer) et (4) (à l'aide de final) implique que vous ne pouvez pas définir
MAP
dynamiquement à l'exécution. Si vous n'avez pas besoin pour le faire, alors il suffit de déclarerMAP
comme unstatic final HashMap<>
et vous êtes garanti de publication.Dans la pratique, les règles sont simples pour un accès sécurisé à de "ne jamais modifié objets":
Si vous êtes à la publication d'un objet qui n'est pas intrinsèquement immuable (comme dans tous les domaines, a déclaré
final
) et:final
champ (y comprisstatic final
pour les membres statiques).Que c'est!
Dans la pratique, il est très efficace. L'utilisation d'un
static final
champ, par exemple, permet à la JVM d'assumer la valeur est inchangée pour la vie du programme et de l'optimiser fortement. L'utilisation d'unfinal
champ de membre permet plus architectures à lire le champ d'une manière équivalente à un champ normal de lire et de ne pas inhiber d'autres optimisationsc.Enfin, l'utilisation de
volatile
ne avoir un certain impact: pas de matériel de barrière est nécessaire sur de nombreuses architectures (tels que les x86, en particulier celles qui ne permettent pas de lit pour passer lit), mais certains d'optimisation et de réorganisation ne peut pas se produire au moment de la compilation - mais cet effet est généralement de petite taille. En échange, vous obtenez en fait plus que ce que vous avez demandé - non seulement vous pouvez en toute sécurité de publier unHashMap
, vous pouvez stocker autant de plus pour ne pas modifiésHashMap
s que vous le souhaitez à la même référence, et soyez assuré que tous les lecteurs verront une façon sécuritaire carte publiée.Pour plus de détails sanglants, reportez-vous à Shipilev ou cette FAQ par Manson et Goetz.
[1] Directement citant shipilev.
a Qui a l'air compliqué, mais ce que je veux dire, c'est que vous pouvez attribuer la référence au moment de la construction, soit au point de déclaration ou dans le constructeur (membre de champs) ou d'initialiseur statique (champs statiques).
b en option, vous pouvez utiliser un
synchronized
méthode get/set, ou unAtomicReference
ou quelque chose, mais nous parlons de la durée de travail minimale que vous pouvez faire.c sur Certaines architectures, avec de très faibles modèles de mémoire (je suis à la recherche d' vous, Alpha) peut exiger un certain type de lecture de la barrière avant un
final
lire - mais ils sont très rares aujourd'hui.never modify
HashMap
signifie pas lastate of the map object
est thread-safe, je pense. Dieu sait la mise en œuvre de bibliothèque, si le document officiel ne dit pas qu'il est thread-safe.get()
pourrait en effet effectuer certaines écrit, dire la mise à jour des statistiques (ou, dans le cas de l'accès ordonnéLinkedHashMap
la mise à jour de l'accès à la commande). Tellement bien écrit de classe doit fournir une documentation qui précise si ...const
sont vraiment en lecture seule dans un sens (en interne, ils peuvent toujours effectuer des écritures, mais ceux-ci devront être thread-safe). Il n'y a pas deconst
mot-clé en Java et je ne suis pas au courant de tout documenté couverture de garantie, mais en général de la bibliothèque standard classes se comporter comme prévu, et les exceptions sont documentées (voir laLinkedHashMap
exemple où RO ops commeget
sont explicitement mentionnées comme dangereux).HashMap
nous avons droit dans la documentation le fil de sécurité comportement de cette classe: Si plusieurs threads accèdent à une carte de hachage simultanément, et au moins l'un des threads modifie la carte structurellement, il doit être synchronisé à l'extérieur. (Une modification structurelle est une opération qui ajoute ou supprime un ou plusieurs mappages, il suffit de modifier la valeur associée à une clé qu'une instance contient déjà n'est pas une modification structurelle.)HashMap
méthodes que nous nous attendons à être en lecture seule, sont en lecture seule, car ils ne sont pas structurellement modifier leHashMap
. Bien sûr, cette garantie pourrait ne pas tenir pour arbitraire autresMap
implémentations, mais la question est à proposHashMap
spécifiquement.HashMap
avant d'invoquerSomeClass.setMap
sur le maintenant volatilsSomeClass.MAP
champ doivent être synchronisés, d'une certaine façon à s'assurer il n'y a pas de réordonnancement des instructions relativement à la loi de stockage nouvellement construitHashMap
survolatile
SomeClass.MAP
? Cet article suggère que le " HashMap` 'mettre les`s doivent être synchronisés pour s'assurer que les autres threads voir le fonctionnement interne de la carte dans un état cohérent, en plus volatile.Jeremy Manson, le dieu quand il s'agit de la Java du Modèle de Mémoire, a trois partie blog sur ce sujet - car en essence, vous êtes vous poser la question "Est-il sûr d'accéder à un immuable HashMap" - la réponse est oui. Mais vous devez répondre à ce prédicat à la question est - Est-ce que mon HashMap immuable". La réponse pourrait vous surprendre: Java est relativement compliqué ensemble de règles pour déterminer l'immuabilité.
Pour plus d'info sur le sujet, lire Jérémy du blog:
La partie 1 sur l'Immuabilité en Java:
http://jeremymanson.blogspot.com/2008/04/immutability-in-java.html
La partie 2 sur l'Immuabilité en Java:
http://jeremymanson.blogspot.com/2008/07/immutability-in-java-part-2.html
La partie 3 sur l'Immuabilité en Java:
http://jeremymanson.blogspot.com/2008/07/immutability-in-java-part-3.html
Les lectures sont à l'abri d'une synchronisation point de vue, mais pas d'un point de vue de la mémoire. C'est quelque chose qui est très mal compris parmi les développeurs Java, y compris ici sur Stackoverflow. (Observer la notation de cette réponse de la preuve.)
Si vous avez d'autres threads en cours d'exécution, ils ne peuvent pas voir une copie mise à jour de la table de hachage si il n'y a pas de mémoire d'écrire du thread courant. Mémoire écrit se produire à travers l'utilisation de la synchronisation ou de la volatilité des mots-clés, ou par l'intermédiaire d'utilisations de certaines java simultanéité des constructions.
Voir Brian Goetz de l'article sur le nouveau Modèle Mémoire Java pour plus de détails.
happens-before
relation par l'intermédiaire d'un volatile membre ou le dernier membre à l'intérieur d'un constructeur.Après un peu plus de recherche, j'ai trouvé cela dans le java doc (l'emphase est mienne):
Cela semble impliquer qu'elle sera sûre, en supposant que la converse de la déclaration, il est vrai.
L'on remarque est que, dans certaines circonstances, un get() à partir d'une désynchronisation du HashMap peut provoquer une boucle infinie. Cela peut se produire si un concurrent à mettre() provoque une resucée de la Carte.
http://lightbody.net/blog/2005/07/hashmapget_can_cause_an_infini.html
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.
Il y a une nuance importante cependant. Il est sûr d'accéder à la carte, mais en général, il n'est pas garanti que tous les threads de voir exactement le même état (et donc des valeurs) de la table de hachage. Cela peut se produire sur des systèmes multiprocesseurs où les modifications de la table de hachage fait par un thread (par exemple, le seul qui l'ont peuplée) pouvez vous asseoir dans que cache du CPU et de ne pas être vus par les threads s'exécutant sur d'autres Processeurs, jusqu'à ce qu'un mémoire en clôture de l'opération est effectuée assurer la cohérence de cache. Le Langage Java est la Spécification explicite sur ce point: la solution est d'acquérir un verrou (synchronisée (...)) qui émet un mémoire en clôture de l'opération. Donc, si vous êtes sûr qu'après le remplissage de la table de hachage chacun des fils acquiert un verrou, alors c'est OK à partir de ce point pour accéder à la table de hachage à partir de n'importe quel thread jusqu'à ce que la table de hachage est à nouveau modifiée.
Selon http://www.ibm.com/developerworks/java/library/j-jtp03304/ # Initialisation de la sécurité, vous pouvez faire votre HashMap un champ final et après le constructeur finitions, il serait en toute sécurité publié.
...
En vertu du nouveau modèle de mémoire, il y a quelque chose de semblable à un passe-avant la relation entre l'écriture d'un champ final dans un constructeur et la charge initiale d'une référence partagée à cet objet dans un autre thread.
...
Si le scénario que vous avez décrit, c'est que vous avez besoin de mettre un paquet de données dans une Carte, puis lorsque vous avez terminé le remplissage, il vous traiter comme immuable. Une approche qui est "safe" (ce qui signifie que vous êtes en appliquant ce que c'est vraiment traité comme immuable) est de remplacer la référence à
Collections.unmodifiableMap(originalMap)
lorsque vous êtes prêt à le faire immuable.Pour un exemple de la façon dont mal de cartes peut échouer si elle est utilisée simultanément, et la solution de contournement proposée je l'ai mentionné, découvrez ce bug parade entrée: bug_id=6423457
Être averti que, même en single-threaded code, le remplacement d'un ConcurrentHashMap avec une table de hachage peut-être pas sûr. ConcurrentHashMap interdit la valeur null comme une clé ou une valeur. HashMap ne pas les interdire (ne demandez pas).
Donc dans la situation peu probable que votre code existant peut ajouter une valeur null à la collecte lors de l'installation (sans doute dans un cas d'échec d'un certain type), en remplacement de la collection, comme décrit va changer le comportement fonctionnel.
Cela dit, à condition de ne rien faire d'autre simultanées lit à partir d'une table de hachage sont en sécurité.
[Edit: par "concurrent lit", je veux dire qu'il n'y a pas les modifications concurrentes.
D'autres réponses expliquer comment s'en assurer. Une façon est de faire la carte immuable, mais il n'est pas nécessaire. Par exemple, le JSR133 modèle de mémoire définit explicitement le démarrage d'un thread pour être une des actions synchronisées, ce qui signifie que les modifications apportées dans Un thread avant qu'il ne commence thread B sont visibles dans le fil B.
Mon intention n'est pas en contradiction avec celles des réponses plus détaillées à propos de la Java du Modèle de Mémoire. Cette réponse est destinée à souligner que même en dehors de problèmes de concurrence, il y a au moins une API différence entre ConcurrentHashMap et de la table de hachage, ce qui pourrait saborder même un single-threaded programme qui a remplacé l'un avec l'autre.]
http://www.docjar.com/html/api/java/util/HashMap.java.html
ici est la source de la table de hachage. Comme vous pouvez le dire, il n'y a absolument pas de verrouillage /mutex code.
Cela signifie que tout bien pour lire à partir d'une table de hachage dans un multithread situation, je serais certainement utiliser un ConcurrentHashMap si il y avait de multiples écritures.
Ce qui est intéressant, c'est que les deux .NET HashTable et Dictionnaire<K,V> ont construit dans le code de synchronisation.
Si l'initialisation et le tout est synchronisé vous enregistrer.
Code suivant est sauver parce que le chargeur de classe de prendre soin de la synchronisation:
Code suivant est de sauvegarder, car l'écriture de la volatilité va prendre soin de la synchronisation.
Cela fonctionnera également si le membre est une variable final à cause finale est aussi volatile. Et si la méthode est un constructeur.