web-dev-qa-db-fra.com

Quelle est la différence entre ConcurrentHashMap et Collections.synchronizedMap (Map)?

J'ai une carte qui doit être modifiée par plusieurs threads simultanément.

Il semble y avoir trois implémentations de cartes synchronisées différentes dans l'API Java:

  • Hashtable
  • Collections.synchronizedMap(Map)
  • ConcurrentHashMap

D'après ce que j'ai compris, Hashtable est une ancienne implémentation (extension de la classe obsolète Dictionary), qui a été adaptée ultérieurement pour s'adapter à l'interface Map. Bien que soit synchronisé, il semble que le problème soit sérieux problèmes d’évolutivité et il est déconseillé pour les nouveaux projets.

Mais qu'en est-il des deux autres? Quelles sont les différences entre les cartes renvoyées par Collections.synchronizedMap(Map) et ConcurrentHashMaps? Lequel correspond à quelle situation?

567
Henning

Pour vos besoins, utilisez ConcurrentHashMap. Il permet la modification simultanée de la carte à partir de plusieurs threads sans qu'il soit nécessaire de les bloquer. Collections.synchronizedMap(map) crée une mappe de blocage qui dégradera les performances, tout en garantissant la cohérence (si utilisé correctement).

Utilisez la deuxième option si vous devez assurer la cohérence des données et si chaque thread doit avoir une vue à jour de la carte. Utilisez le premier si les performances sont critiques et que chaque thread insère uniquement des données dans la carte, les lectures étant moins fréquentes.

397
Yuval Adam
╔═══════════════╦═══════════════════╦═══════════════════╦═════════════════════╗
║   Property    ║     HashMap       ║    Hashtable      ║  ConcurrentHashMap  ║
╠═══════════════╬═══════════════════╬═══════════════════╩═════════════════════╣ 
║      Null     ║     allowed       ║              not allowed                ║
║  values/keys  ║                   ║                                         ║
╠═══════════════╬═══════════════════╬═════════════════════════════════════════╣
║Is thread-safe ║       no          ║                  yes                    ║
╠═══════════════╬═══════════════════╬═══════════════════╦═════════════════════╣
║     Lock      ║       not         ║ locks the whole   ║ locks the portion   ║        
║  mechanism    ║    applicable     ║       map         ║                     ║ 
╠═══════════════╬═══════════════════╩═══════════════════╬═════════════════════╣
║   Iterator    ║               fail-fast               ║ weakly consistent   ║ 
╚═══════════════╩═══════════════════════════════════════╩═════════════════════╝

En ce qui concerne le mécanisme de verrouillage: Hashtableverrouille l'objet , alors que ConcurrentHashMap verrouille uniquement le compartiment .

222
Sergii Shevchyk

Les "problèmes d’évolutivité" de Hashtable se présentent exactement de la même manière dans Collections.synchronizedMap(Map) - ils utilisent une synchronisation très simple, ce qui signifie qu’un seul thread peut accéder à la carte en même temps. 

Ce n'est pas vraiment un problème lorsque vous avez de simples insertions et des recherches (sauf si vous le faites de manière extrêmement intensive), mais cela devient un gros problème lorsque vous devez parcourir l'ensemble de la carte, ce qui peut prendre beaucoup de temps pour une grande un fil fait cela, tous les autres doivent attendre s'ils veulent insérer ou rechercher quoi que ce soit.

La ConcurrentHashMap utilise des techniques très sophistiquées pour réduire le besoin de synchronisation et permettre un accès en lecture parallèle à plusieurs threads sans synchronisation. Elle fournit surtout une Iterator ne nécessitant aucune synchronisation et permettant même la modification de si les éléments insérés lors de l'itération seront retournés).

136
Michael Borgwardt

ConcurrentHashMap est préférable lorsque vous pouvez l'utiliser - même si cela nécessite au moins Java 5.

Il est conçu pour évoluer correctement lorsqu'il est utilisé par plusieurs threads. Les performances peuvent être légèrement inférieures lorsque seulement un seul thread accède à la carte à la fois, mais nettement meilleures lorsque plusieurs threads accèdent à la carte simultanément.

J'ai trouvé un entrée de blog qui reproduit un tableau de l'excellent livre Java Concurrency In Practice , que je recommande vivement.

Collections.synchronizedMap n’a vraiment de sens que si vous avez besoin d’envelopper une carte avec d’autres caractéristiques, par exemple une carte ordonnée, comme une TreeMap.

33
Bill Michell

La principale différence entre ces deux méthodes est que ConcurrentHashMap ne verrouille qu'une partie des données en cours de mise à jour, tandis que d'autres parties peuvent accéder à d'autres données. Cependant, Collections.synchronizedMap() verrouille toutes les données lors de la mise à jour, les autres threads ne peuvent accéder aux données que lorsque le verrou est libéré. S'il existe de nombreuses opérations de mise à jour et un nombre relativement faible d'opérations de lecture, vous devez choisir ConcurrentHashMap.

De plus, une autre différence est que ConcurrentHashMap ne conservera pas l'ordre des éléments dans la carte transmise. Il est similaire à HashMap lors du stockage de données. Il n'y a aucune garantie que l'ordre des éléments est préservé. Alors que Collections.synchronizedMap() préservera l'ordre des éléments de la carte transmise. Par exemple, si vous passez TreeMap à ConcurrentHashMap, l'ordre des éléments dans ConcurrentHashMap ne sera peut-être pas le même que celui de TreeMap, mais Collections.synchronizedMap() préservera cet ordre.

De plus, ConcurrentHashMap peut garantir qu'il n'y a pas de ConcurrentModificationException levée lorsqu'un thread met à jour la carte et qu'un autre thread traverse l'itérateur obtenu à partir de la carte. Cependant, Collections.synchronizedMap() n'est pas garanti à ce sujet.

Il y a un poste qui montre les différences entre eux et aussi la ConcurrentSkipListMap.

32
PixelsTech

Dans ConcurrentHashMap, le verrou est appliqué à un segment au lieu d'une carte entière… .. Chaque segment gère sa propre table de hachage interne Le verrou est appliqué uniquement pour les opérations de mise à jour. Collections.synchronizedMap(Map) synchronise la totalité de la carte.

12
Satish

Comme d'habitude, il existe des compromis entre la concurrence, les frais généraux et la vitesse. Vous devez vraiment prendre en compte les exigences détaillées en matière de simultanéité de votre application pour prendre une décision, puis tester votre code pour voir s'il est assez bon.

11
Zach Scrivena

Vous avez raison à propos de HashTable, vous pouvez l’oublier.

Votre article mentionne le fait que, bien que HashTable et la classe d'encapsulation synchronisée fournissent une sécurité de thread de base en permettant uniquement à un thread d'accéder à la carte, il ne s'agit pas d'une sécurité de thread "vraie", car de nombreuses opérations composées nécessitent encore synchronisation supplémentaire, par exemple:

synchronized (records) {
  Record rec = records.get(id);
  if (rec == null) {
      rec = new Record(id);
      records.put(id, rec);
  }
  return rec;
}

Cependant, ne pensez pas que ConcurrentHashMap est une alternative simple à un HashMap avec un bloc synchronized typique, comme indiqué ci-dessus. Lisez ceci article pour mieux comprendre ses subtilités.

9
eljenso

Carte synchronisée:

Synchronized Map n’est également pas très différent de Hashtable et offre des performances similaires dans les programmes Java simultanés. La seule différence entre Hashtable et SynchronizedMap est que SynchronizedMap n’est pas un héritage et vous pouvez envelopper toute Map pour en créer la version synchronisée à l’aide de la méthode Collections.synchronizedMap ().

ConcurrentHashMap:

La classe ConcurrentHashMap fournit une version simultanée de HashMap standard. Il s'agit d'une amélioration de la fonctionnalité synchronizedMap fournie dans la classe Collections.

Contrairement à Hashtable et Synchronized Map, il ne verrouille jamais l'ensemble de la carte. Il divise la carte en segments et le verrouillage est effectué sur ceux-ci. Cela fonctionne mieux si le nombre de threads de lecteur est supérieur au nombre de threads d'écriture.

ConcurrentHashMap est par défaut séparé en 16 régions et des verrous sont appliqués. Ce numéro par défaut peut être défini lors de l'initialisation d'une instance de ConcurrentHashMap. Lors du paramétrage de données dans un segment particulier, le verrou pour ce segment est obtenu. Cela signifie que deux mises à jour peuvent toujours être exécutées simultanément en toute sécurité si elles affectent chacune des compartiments distincts, minimisant ainsi les conflits de verrous et optimisant les performances.

ConcurrentHashMap ne lève pas une ConcurrentModificationException 

ConcurrentHashMap ne lève pas d'exception ConcurrentModificationException si un thread tente de le modifier pendant qu'un autre itère dessus

Différence entre synchornizedMap et ConcurrentHashMap

Collections.synchornizedMap (HashMap) retournera une collection presque équivalente à Hashtable, où chaque opération de modification sur la carte est verrouillée sur un objet Map, tandis que dans le cas de ConcurrentHashMap, la sécurité des threads est obtenue en divisant la carte entière en une partition différente en fonction du niveau de concurrence et verrouiller uniquement une partie particulière au lieu de verrouiller toute la carte.

ConcurrentHashMap n'autorise pas les clés NULL ni les valeurs NULL tandis que HashMap synchronisé autorise une clé NULL.

Liens similaires

Link1

Link2

Comparaison des performances

8
Ramesh Papaganti

En voici quelques uns:

1) ConcurrentHashMap verrouille uniquement une partie de la carte, mais SynchronizedMap verrouille l'ensemble de MAp.
2) ConcurrentHashMap a de meilleures performances que SynchronizedMap et est plus évolutif.
3) En cas de lecteurs multiples et d’écrivain unique, ConcurrentHashMap est le meilleur choix.

Ce texte est de Différence entre ConcurrentHashMap et hashtable en Java

7
Raj

Nous pouvons atteindre la sécurité des threads en utilisant ConcurrentHashMap et synchronizedHashmap et Hashtable. Mais il y a beaucoup de différence si vous regardez leur architecture.

  1. synchronizedHashmap et Hashtable

Les deux maintiendront le verrou au niveau de l'objet. Donc, si vous souhaitez effectuer une opération telle que put/get, vous devez d'abord acquérir le verrou. Dans le même temps, les autres threads ne sont autorisés à effectuer aucune opération. Donc, à la fois, un seul thread peut fonctionner sur cela. Donc, le temps d'attente augmentera ici. Nous pouvons dire que les performances sont relativement faibles lorsque vous comparez avec ConcurrentHashMap.

  1. ConcurrentHashMap

Il maintiendra le verrou au niveau du segment. Il comporte 16 segments et maintient le niveau de concurrence de 16 par défaut. Donc, à la fois, 16 threads peuvent fonctionner sur ConcurrentHashMap. De plus, l'opération de lecture ne nécessite pas de verrouillage. Donc, n'importe quel nombre de threads peuvent effectuer une opération get sur celui-ci. 

Si thread1 veut effectuer une opération de vente dans le segment 2 et que thread2 souhaite effectuer une opération de vente sur le segment 4, il est autorisé à le faire ici. Cela signifie que 16 threads peuvent effectuer une opération de mise à jour (insertion/suppression) sur ConcurrentHashMap à la fois.

Alors que le temps d'attente sera moins ici. Par conséquent, les performances sont relativement meilleures que synchronizedHashmap et Hashtable.

7
Sumanth Varada

ConcurrentHashMap

  • Vous devez utiliser ConcurrentHashMap lorsque vous avez besoin d'une très grande simultanéité dans votre projet.
  • Il est thread-safe sans synchroniser toute la carte.
  • Les lectures peuvent être très rapides lorsque l'écriture est effectuée avec un verrou.
  • Il n'y a pas de verrouillage au niveau de l'objet.
  • Le verrouillage a une granularité beaucoup plus fine au niveau du compartiment de hachage.
  • ConcurrentHashMap ne lève pas d'exception ConcurrentModificationException si un thread tente de le modifier pendant qu'un autre l'itère.
  • ConcurrentHashMap utilise une multitude de verrous.

SynchronizedHashMap

  • Synchronisation au niveau de l'objet.
  • Chaque opération de lecture/écriture doit acquérir un verrou.
  • Verrouiller toute la collection est une surcharge de performances.
  • Cela donne essentiellement un seul thread à la carte entière et bloque tous les autres threads.
  • Cela peut provoquer des conflits.
  • SynchronizedHashMap renvoie Iterator, qui échoue lors d'une modification simultanée. 

la source

5
Premraj

ConcurrentHashMap est optimisé pour un accès simultané.

Les accès ne verrouillent pas la totalité de la carte mais utilisent une stratégie plus fine, ce qui améliore l'évolutivité. Il existe également des améliorations fonctionnelles spécifiquement pour l'accès simultané, par exemple. itérateurs simultanés.

4
starblue

Il y a une caractéristique critique à noter à propos de ConcurrentHashMap autre que la fonctionnalité de concurrence qu'il fournit, qui est fail-safe iterator. J'ai vu des développeurs utiliser ConcurrentHashMap simplement parce qu'ils voulaient éditer le entryset - mettre/supprimer en le parcourant. Collections.synchronizedMap(Map) ne fournit pas fail-safe iterator mais fournit échec rapide itérateur à la place. Les itérateurs fail-fast utilisent une capture instantanée de la taille de la carte qui ne peut pas être modifiée lors de l'itération.

3
hi.nitish
  1. Si la cohérence des données est très importante, utilisez Hashtable ou Collections.synchronizedMap (Map).
  2. Si la vitesse/performance est très importante et que la mise à jour des données peut être compromise, utilisez ConcurrentHashMap.
3
Shivam Maharshi

La méthode Collections.synchronizedMap () synchronise toutes les méthodes de HashMap et la réduit efficacement à une structure de données dans laquelle un thread peut entrer à la fois car elle verrouille chaque méthode sur un verrou commun. 

Dans ConcurrentHashMap, la synchronisation se fait un peu différemment. Plutôt que de verrouiller chaque méthode sur un verrou commun, ConcurrentHashMap utilise un verrou séparé pour des compartiments séparés, ne verrouillant ainsi qu'une partie de la carte . Par défaut, il existe 16 compartiments et des verrous distincts pour les compartiments séparés. Le niveau de simultanéité par défaut est donc 16. Cela signifie théoriquement qu’un moment donné, 16 threads peuvent accéder à ConcurrentHashMap s’ils passent tous à des compartiments distincts.

1
infoj

En général, si vous voulez utiliser la variable ConcurrentHashMap, assurez-vous d'être prêt à manquer les 'mises à jour'.
(c’est-à-dire que l’impression du contenu de HashMap ne garantit pas l’impression de la carte mise à jour) et utilise des API telles que CyclicBarrier pour assurer la cohérence du cycle de vie de votre programme.

1
Kounavi

Outre ce qui a été suggéré, j'aimerais publier le code source lié à SynchronizedMap.

Pour sécuriser un thread Map, nous pouvons utiliser l'instruction Collections.synchronizedMap et entrer l'instance de carte en tant que paramètre.

L'implémentation de synchronizedMap dans Collections est comme ci-dessous 

   public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m) {
        return new SynchronizedMap<>(m);
    }

Comme vous pouvez le constater, l'objet Map en entrée est encapsulé par l'objet SynchronizedMap.
Creusons dans l'implémentation de SynchronizedMap

 private static class SynchronizedMap<K,V>
        implements Map<K,V>, Serializable {
        private static final long serialVersionUID = 1978198479659022715L;

        private final Map<K,V> m;     // Backing Map
        final Object      mutex;        // Object on which to synchronize

        SynchronizedMap(Map<K,V> m) {
            this.m = Objects.requireNonNull(m);
            mutex = this;
        }

        SynchronizedMap(Map<K,V> m, Object mutex) {
            this.m = m;
            this.mutex = mutex;
        }

        public int size() {
            synchronized (mutex) {return m.size();}
        }
        public boolean isEmpty() {
            synchronized (mutex) {return m.isEmpty();}
        }
        public boolean containsKey(Object key) {
            synchronized (mutex) {return m.containsKey(key);}
        }
        public boolean containsValue(Object value) {
            synchronized (mutex) {return m.containsValue(value);}
        }
        public V get(Object key) {
            synchronized (mutex) {return m.get(key);}
        }

        public V put(K key, V value) {
            synchronized (mutex) {return m.put(key, value);}
        }
        public V remove(Object key) {
            synchronized (mutex) {return m.remove(key);}
        }
        public void putAll(Map<? extends K, ? extends V> map) {
            synchronized (mutex) {m.putAll(map);}
        }
        public void clear() {
            synchronized (mutex) {m.clear();}
        }

        private transient Set<K> keySet;
        private transient Set<Map.Entry<K,V>> entrySet;
        private transient Collection<V> values;

        public Set<K> keySet() {
            synchronized (mutex) {
                if (keySet==null)
                    keySet = new SynchronizedSet<>(m.keySet(), mutex);
                return keySet;
            }
        }

        public Set<Map.Entry<K,V>> entrySet() {
            synchronized (mutex) {
                if (entrySet==null)
                    entrySet = new SynchronizedSet<>(m.entrySet(), mutex);
                return entrySet;
            }
        }

        public Collection<V> values() {
            synchronized (mutex) {
                if (values==null)
                    values = new SynchronizedCollection<>(m.values(), mutex);
                return values;
            }
        }

        public boolean equals(Object o) {
            if (this == o)
                return true;
            synchronized (mutex) {return m.equals(o);}
        }
        public int hashCode() {
            synchronized (mutex) {return m.hashCode();}
        }
        public String toString() {
            synchronized (mutex) {return m.toString();}
        }

        // Override default methods in Map
        @Override
        public V getOrDefault(Object k, V defaultValue) {
            synchronized (mutex) {return m.getOrDefault(k, defaultValue);}
        }
        @Override
        public void forEach(BiConsumer<? super K, ? super V> action) {
            synchronized (mutex) {m.forEach(action);}
        }
        @Override
        public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
            synchronized (mutex) {m.replaceAll(function);}
        }
        @Override
        public V putIfAbsent(K key, V value) {
            synchronized (mutex) {return m.putIfAbsent(key, value);}
        }
        @Override
        public boolean remove(Object key, Object value) {
            synchronized (mutex) {return m.remove(key, value);}
        }
        @Override
        public boolean replace(K key, V oldValue, V newValue) {
            synchronized (mutex) {return m.replace(key, oldValue, newValue);}
        }
        @Override
        public V replace(K key, V value) {
            synchronized (mutex) {return m.replace(key, value);}
        }
        @Override
        public V computeIfAbsent(K key,
                Function<? super K, ? extends V> mappingFunction) {
            synchronized (mutex) {return m.computeIfAbsent(key, mappingFunction);}
        }
        @Override
        public V computeIfPresent(K key,
                BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
            synchronized (mutex) {return m.computeIfPresent(key, remappingFunction);}
        }
        @Override
        public V compute(K key,
                BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
            synchronized (mutex) {return m.compute(key, remappingFunction);}
        }
        @Override
        public V merge(K key, V value,
                BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
            synchronized (mutex) {return m.merge(key, value, remappingFunction);}
        }

        private void writeObject(ObjectOutputStream s) throws IOException {
            synchronized (mutex) {s.defaultWriteObject();}
        }
    }

Ce que SynchronizedMap fait peut être résumé en ajoutant un verrou unique à la méthode principale de l’objet Map. Toutes les méthodes protégées par le verrou ne sont pas accessibles simultanément par plusieurs threads. Cela signifie que des opérations normales telles que put et get peuvent être exécutées par un seul thread en même temps pour toutes les données de l'objet Map

Cela rend le thread d'objet Map sûr maintenant, mais les performances peuvent devenir un problème dans certains scénarios. 

La ConcurrentMap est beaucoup plus compliquée dans la mise en œuvre, on peut se référer à Construire un meilleur HashMap pour plus de détails. En un mot, il est mis en œuvre en tenant compte à la fois de la sécurité des threads et des performances. 

0
Gearon