2
votes

La méthode contientKey de Hashmap est-elle threadsafe si la carte est initialisée une fois et n'est plus jamais modifiée

Pouvons-nous utiliser la méthode containsKey () de Hashmap sans synchroniser dans un environnement multi-thread?

Remarque: les threads ne liront que le Hashmap. La carte est initialisée une fois et n'est plus jamais modifiée.


0 commentaires

7 Réponses :


1
votes

Non. (Non, pas du tout. Pas du tout. 30 caractères?)


0 commentaires

2
votes

Non, il n'est thread-safe pour aucune opération. Vous devez synchroniser tous les accès ou utiliser quelque chose comme ConcurrentHashMap .

Mon histoire d'horreur de dépannage de système de production préférée est lorsque nous avons découvert que HashMap.get était entré dans une boucle infinie bloquant 100% du processeur pour toujours en raison d'une synchronisation manquante. Cela s'est produit parce que les listes liées utilisées dans chaque compartiment sont passées dans un état incohérent. La même chose pourrait se produire avec containsKey .

Vous devriez être en sécurité si personne ne modifie le HashMap après sa publication initiale, mais mieux utiliser une implémentation qui le garantit explicitement (comme ImmutableMap ou, encore une fois, un ConcurrentMap).


0 commentaires

1
votes

C'est compliqué, mais surtout non.

  1. La spécification de HashMap ne donne aucune garantie. Il se réserve donc le droit de faire sauter le yankee doodle dandy de vos haut-parleurs si vous essayez: vous n'êtes tout simplement pas censé l'utiliser de cette façon.

  2. ... cependant, en pratique, alors que l'API de HashMap ne donne aucune garantie, cela fonctionne généralement. Mais attention à l'histoire d'horreur de la réponse de @ Thilo.

  3. ... buuut, le modèle de mémoire Java fonctionne comme ceci: Vous devez considérer que chaque thread obtient une copie individuelle de chaque champ sur tout le tas de la VM. Ces copies individuelles sont ensuite synchronisées à des moments indéterminés. Cela signifie que toutes sortes de codes ne fonctionneront tout simplement pas correctement; vous ajoutez une entrée à la carte à partir d'un thread, et si vous accédez ensuite à cette carte à partir d'un autre, vous ne la verrez pas même si beaucoup de temps s'est écoulé - c'est théoriquement possible. De plus, en interne, map utilise plusieurs champs et vraisemblablement ces champs doivent être cohérents les uns avec les autres, sinon vous obtiendrez des comportements étranges (exceptions et résultats erronés). Le JMM ne garantit pas non plus la cohérence. Le moyen de sortir de ce dilemme est que le JMM propose ces choses appelées relations «avant / après» qui vous donnent la garantie que les changements ont été synchronisés. L'utilisation du mot-clé "synchronisé" est un moyen simple de créer de telles relations.

  4. Pourquoi ne pas utiliser un ConcurrentHashMap qui a toutes les cloches et sifflets intégrés et garantit en fait que l'ajout d'une entrée à partir du thread A, puis l'interrogation via containsKey du thread B vous donnera une réponse cohérente (qui pourrait toujours être `` non, cette clé n'est pas dans la carte '', car peut-être que le thread B est arrivé légèrement avant le thread A ou légèrement après, mais il n'y a aucun moyen pour vous de le savoir. Cela ne lèvera aucune exception ni ne fera quelque chose de vraiment bizarre, comme renvoyer «faux» pour des choses que vous avez ajoutées il y a des siècles tout d'un coup).

Donc, même si c'est compliqué, la réponse est essentiellement: ne faites pas ça; soit utiliser une garde synchronisée, soit probablement le meilleur choix: ConcurrentHashMap .


0 commentaires

1
votes

Non, lisez la partie gras de Documentation HashMap :

Notez que cette mise en œuvre n'est pas synchronisée.

Vous devriez donc le gérer:

Si plusieurs threads accèdent à une carte de hachage simultanément et qu'au moins un des threads modifie la carte structurellement, il doit être synchronisé en externe.

Et des solutions suggérées:

Ceci est généralement accompli en synchronisant sur un objet qui encapsule naturellement la carte. Si aucun objet de ce type n’existe, la carte doit être «encapsulée» à l’aide de la méthode Collections.synchronizedMap


0 commentaires

3
votes

Vous ne devriez pas regarder une seule méthode de cette façon. Un HashMap n'est pas destiné à être utilisé dans une configuration multithread.

Cela dit, la seule exception serait: une carte qui est créée une fois (thread unique), et qui est ensuite "en lecture" uniquement. En d'autres termes: si une carte n'est plus modifiée , vous pouvez demander à autant de fils de lire que vous le souhaitez.

De ce point de vue, les appels à containsKey () ne devraient pas poser de problème. Le problème survient lorsque les informations sur lesquelles cette méthode repose changent avec le temps.


0 commentaires

6
votes

Cela dépend vraiment de la manière / du moment où vous accédez à votre carte.
En supposant que la carte est initialisée une fois, et jamais modifiée à nouveau, alors les méthodes qui ne modifient pas l'état interne comme containsKey () devraient être sûres. Dans ce cas, vous devez vous assurer que votre carte est vraiment immuable et publiée en toute sécurité.

Maintenant, si dans votre cas particulier, l'état change au cours de votre programme, alors non, ce n'est pas sûr. Depuis la documentation :

Notez que cette implémentation n'est pas synchronisée. Si plusieurs threads accèdent à une carte de hachage simultanément et qu'au moins un des threads modifie la carte structurellement, il doit être synchronisé en externe.

Dans ce cas, vous devez utiliser ConcurrentHashMap ou effectuer une synchronisation externe.


1 commentaires

Merci pour votre réponse yaken.



1
votes

@ user7294900 a raison.

Si votre application ne modifie pas structurellement le HashMap qui est construit en toute sécurité avec les threads et que votre application appelle simplement la méthode containsKey, c'est thread-safe.

Par exemple, je ' J'ai utilisé HashMap comme ceci:

@Component
public class SpringSingletonBean {

    private Map<String, String> map = new HashMap<>();

    public void doSomething() {
        //
        if (map.containsKey("aaaa")) {
            //do something
        }
    }

    @PostConstruct
    public void init() {
        // do something to initialize the map
    }

}

Cela fonctionne bien.


0 commentaires