it-swarm.com.de

Was ist der Unterschied zwischen ConcurrentHashMap und Collections.synchronizedMap (Map)?

Ich habe eine Map die von mehreren Threads gleichzeitig modifiziert werden soll.

Es scheint drei verschiedene synchronisierte Map-Implementierungen in der Java-API zu geben:

  • Hashtable
  • Collections.synchronizedMap(Map)
  • ConcurrentHashMap

Soweit ich weiß, ist Hashtable eine alte Implementierung (Erweiterung der veralteten Klasse Dictionary), die später an die Schnittstelle Map angepasst wurde. Während es ist synchronisiert, scheint es ernsthafte Skalierbarkeitsprobleme zu haben und wird für neue Projekte abgeraten.

Aber was ist mit den beiden anderen? Was sind die Unterschiede zwischen Maps, die von Collections.synchronizedMap(Map) und ConcurrentHashMaps zurückgegeben werden? Welches passt zu welcher Situation?

567
Henning

Verwenden Sie für Ihre Bedürfnisse ConcurrentHashMap. Es ermöglicht die gleichzeitige Änderung der Map aus mehreren Threads, ohne dass diese blockiert werden müssen. Collections.synchronizedMap(map) erstellt eine blockierende Map, die die Leistung beeinträchtigt, die Konsistenz jedoch gewährleistet (wenn sie ordnungsgemäß verwendet wird).

Verwenden Sie die zweite Option, wenn Sie die Konsistenz der Daten sicherstellen möchten und jeder Thread eine aktuelle Ansicht der Karte haben muss. Verwenden Sie die erste Option, wenn die Leistung von entscheidender Bedeutung ist und jeder Thread nur Daten in die Karte einfügt.

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   ║ 
╚═══════════════╩═══════════════════════════════════════╩═════════════════════╝

Zum Sperrmechanismus: Hashtablesperrt das Objekt , während ConcurrentHashMapnur den Bucket sperrt.

222
Sergii Shevchyk

Die "Skalierbarkeitsprobleme" für Hashtable sind in Collections.synchronizedMap(Map) auf die gleiche Weise vorhanden - sie verwenden eine sehr einfache Synchronisation, was bedeutet, dass nur ein Thread gleichzeitig auf die Karte zugreifen kann. 

Dies ist bei einfachen Einfügungen und Suchvorgängen kein großes Problem (es sei denn, Sie tun dies extrem intensiv), wird jedoch zu einem großen Problem, wenn Sie die gesamte Map durchlaufen müssen, was für eine große Map lange Zeit in Anspruch nehmen kann Ein Thread tut das, alle anderen müssen warten, wenn sie etwas einfügen oder nachschlagen wollen.

Die ConcurrentHashMap verwendet sehr ausgefeilte Techniken, um den Bedarf an Synchronisation zu reduzieren und parallelen Lesezugriff für mehrere Threads ohne Synchronisation zu ermöglichen. Darüber hinaus bietet sie eine Iterator, die keine Synchronisation erfordert und sogar die Änderung der Map während der Interation zulässt (dies garantiert jedoch keine Gewähr ob Elemente, die während der Iteration eingefügt wurden, zurückgegeben werden oder nicht).

136

ConcurrentHashMap wird bevorzugt, wenn Sie es verwenden können - es ist jedoch mindestens Java 5 erforderlich.

Es lässt sich gut skalieren, wenn es von mehreren Threads verwendet wird. Die Leistung kann geringfügig schlechter sein, wenn jeweils nur ein einzelner Thread auf die Map zugreift, jedoch deutlich besser, wenn mehrere Threads gleichzeitig auf die Map zugreifen.

Ich habe einen Blogeintrag gefunden der eine Tabelle aus dem ausgezeichneten Buch Java Concurrency In Practice reproduziert, die ich durchaus empfehle.

Collections.synchronizedMap ist wirklich nur dann sinnvoll, wenn Sie eine Karte mit anderen Merkmalen zusammenfassen müssen, z. B. mit einer Art geordneter Karte, z.

33
Bill Michell

Der Hauptunterschied zwischen diesen beiden ist, dass ConcurrentHashMap nur einen Teil der Daten sperrt, die gerade aktualisiert werden, während andere Threads auf einen anderen Teil der Daten zugreifen können. Collections.synchronizedMap() sperrt jedoch alle Daten während der Aktualisierung. Andere Threads können nur dann auf die Daten zugreifen, wenn die Sperre aufgehoben wird. Wenn es viele Aktualisierungsvorgänge und relativ wenige Lesevorgänge gibt, sollten Sie ConcurrentHashMap auswählen.

Ein weiterer Unterschied besteht darin, dass ConcurrentHashMap die Reihenfolge der Elemente in der übergebenen Map nicht beibehält. Dies ähnelt HashMap beim Speichern von Daten. Es gibt keine Garantie dafür, dass die Elementreihenfolge erhalten bleibt. Während Collections.synchronizedMap() die Elementreihenfolge der übergebenen Map beibehält. Wenn Sie beispielsweise TreeMap an ConcurrentHashMap übergeben, wird die Elementreihenfolge in ConcurrentHashMap stimmt möglicherweise nicht mit der Reihenfolge in TreeMap überein, aber Collections.synchronizedMap() behält die Reihenfolge bei.

Darüber hinaus kann ConcurrentHashMap garantieren, dass kein ConcurrentModificationException ausgelöst wird, während ein Thread die Karte aktualisiert und ein anderer Thread den von der Karte erhaltenen Iterator durchläuft. Collections.synchronizedMap() kann jedoch nicht garantiert werden.

Es gibt einen Beitrag , der die Unterschiede dieser beiden und auch der ConcurrentSkipListMap demonstriert.

32
PixelsTech

In ConcurrentHashMap wird die Sperre auf ein Segment anstelle einer gesamten Map angewendet. Jedes Segment verwaltet seine eigene interne Hash-Tabelle. Die Sperre gilt nur für Aktualisierungsvorgänge. Collections.synchronizedMap(Map) synchronisiert die gesamte Karte.

12
Satish

Wie üblich gibt es Nebeneffekte - Overhead-Geschwindigkeits-Kompromisse. Sie müssen wirklich die detaillierten Parallelitätsanforderungen Ihrer Anwendung berücksichtigen, um eine Entscheidung zu treffen, und dann den Code testen, um festzustellen, ob er gut genug ist.

11
Zach Scrivena

Du hast recht mit HashTable, du kannst es vergessen.

Ihr Artikel erwähnt die Tatsache, dass HashTable und die synchronisierte Wrapper-Klasse zwar grundlegende Thread-Sicherheit bieten, indem sie jeweils nur einen Thread für den Zugriff auf die Map zulassen, dies jedoch keine "echte" Thread-Sicherheit ist, da viele Verbundoperationen noch erfordern zusätzliche Synchronisation, zum Beispiel:

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

Denken Sie jedoch nicht, dass ConcurrentHashMap eine einfache Alternative für eine HashMap mit einem typischen synchronized-Block ist, wie oben gezeigt. Lesen Sie this article, um seine Feinheiten besser zu verstehen.

9
eljenso

Synchronisierte Karte:

Synchronized Map unterscheidet sich auch nicht sehr von Hashtable und bietet eine ähnliche Leistung in gleichzeitigen Java-Programmen. Der einzige Unterschied zwischen Hashtable und SynchronizedMap besteht darin, dass SynchronizedMap kein Erbe ist. Sie können jede Map umschließen, um ihre synchronisierte Version mithilfe der Collections.synchronizedMap () - Methode zu erstellen.

ConcurrentHashMap:

Die ConcurrentHashMap-Klasse stellt eine gleichzeitige Version der Standard-HashMap bereit. Dies ist eine Verbesserung der synchronizedMap-Funktionalität in der Collections-Klasse.

Im Gegensatz zu Hashtable und Synchronized Map sperrt sie niemals die gesamte Map, sondern teilt die Map in Segmente auf und sperrt diese. Es ist besser, wenn die Anzahl der Reader-Threads größer ist als die Anzahl der Writer-Threads.

ConcurrentHashMap ist standardmäßig in 16 Regionen unterteilt und Sperren werden angewendet. Diese Standardnummer kann beim Initialisieren einer ConcurrentHashMap-Instanz festgelegt werden. Wenn Sie Daten in einem bestimmten Segment einstellen, wird die Sperre für dieses Segment erhalten. Dies bedeutet, dass zwei Aktualisierungen immer noch gleichzeitig sicher ausgeführt werden können, wenn sie sich auf separate Buckets auswirken. Dadurch wird der Konflikt mit Sperren minimiert und die Leistung maximiert.

ConcurrentHashMap löst keine ConcurrentModificationException aus 

ConcurrentHashMap löst keine ConcurrentModificationException aus, wenn ein Thread versucht, sie zu ändern, während ein anderer sie durchläuft

Unterschied zwischen synchornizedMap und ConcurrentHashMap

Collections.synchornizedMap (HashMap) gibt eine Auflistung zurück, die fast gleichbedeutend mit Hashtable ist, in der jeder Änderungsvorgang für Map für Map-Objekte gesperrt ist. Im Falle von ConcurrentHashMap wird die Thread-Sicherheit durch Unterteilen der gesamten Map in verschiedene Partitionen basierend auf der gemeinsamen Ebene erreicht und nur das Sperren eines bestimmten Abschnitts anstelle des Sperrens der gesamten Map.

ConcurrentHashMap erlaubt keine Nullschlüssel oder Nullwerte, während die synchronisierte HashMap einen Nullschlüssel zulässt.

Ähnliche Links

Link1

Link2

Leistungsvergleich

8

Hier sind einige:

1) ConcurrentHashMap sperrt nur einen Teil der Map, aber SynchronizedMap sperrt den gesamten MAp.
2) ConcurrentHashMap hat eine bessere Leistung als SynchronizedMap und ist skalierbarer.
3) Bei Mehrfachleser und Single Writer ist ConcurrentHashMap die beste Wahl.

Dieser Text ist aus Unterschied zwischen ConcurrentHashMap und Hashtable in Java

7
Raj

Wir können Thread-Sicherheit erreichen, indem wir ConcurrentHashMap und synchronisedHashmap und Hashtable verwenden. Es gibt jedoch einen großen Unterschied, wenn Sie ihre Architektur betrachten.

  1. synchronisedHashmap und Hashtable

Beide werden die Sperre auf Objektebene beibehalten. Wenn Sie also eine Operation wie put/get ausführen möchten, müssen Sie zuerst die Sperre erwerben. Zur gleichen Zeit dürfen andere Threads keine Operation ausführen. Es kann also immer nur ein Thread mit diesem Vorgang arbeiten. Die Wartezeit wird sich also hier erhöhen. Wir können sagen, dass die Leistung im Vergleich zu ConcurrentHashMap relativ niedrig ist.

  1. ConcurrentHashMap

Die Sperre bleibt auf Segmentebene erhalten. Es hat 16 Segmente und behält die Parallelität standardmäßig als 16 bei. So können 16 Threads gleichzeitig mit ConcurrentHashMap arbeiten. Darüber hinaus erfordert die Leseoperation keine Sperre. Es können also beliebig viele Threads eine get-Operation ausführen. 

Wenn Thread1 die Put-Operation in Segment 2 ausführen möchte und Thread2 die Put-Operation in Segment 4 ausführen möchte, ist dies hier zulässig. Dies bedeutet, dass 16 Threads gleichzeitig Aktualisierungsvorgänge (put/delete) für ConcurrentHashMap durchführen können.

Damit wird die Wartezeit hier geringer. Daher ist die Leistung relativ besser als synchronizedHashmap und Hashtable.

7
Sumanth Varada

ConcurrentHashMap

  • Sie sollten ConcurrentHashMap verwenden, wenn Sie in Ihrem Projekt eine sehr hohe Parallelität benötigen.
  • Es ist threadsicher, ohne die gesamte Map zu synchronisieren.
  • Das Lesen kann sehr schnell erfolgen, während das Schreiben mit einer Sperre erfolgt.
  • Es gibt keine Verriegelung auf Objektebene.
  • Die Sperrung ist auf der Ebene des Hashmap-Eimers viel feiner.
  • ConcurrentHashMap löst keine ConcurrentModificationException aus, wenn ein Thread versucht, sie zu ändern, während ein anderer sie durchläuft.
  • ConcurrentHashMap verwendet eine Vielzahl von Sperren.

SynchronizedHashMap

  • Synchronisation auf Objektebene.
  • Jede Lese-/Schreiboperation muss eine Sperre erhalten.
  • Das Sperren der gesamten Sammlung ist ein Performance-Overhead.
  • Dies gibt im Wesentlichen nur Zugriff auf einen Thread für die gesamte Map und blockiert alle anderen Threads.
  • Dies kann zu Konflikten führen.
  • SynchronizedHashMap gibt Iterator zurück, der bei gleichzeitiger Änderung schnell ausfällt. 

Quelle

5
Premraj

ConcurrentHashMap ist für gleichzeitigen Zugriff optimiert.

Zugriffe sperren nicht die gesamte Karte, sondern verwenden eine feinere Strategie, die die Skalierbarkeit verbessert. Es gibt auch funktionale Verbesserungen speziell für den gleichzeitigen Zugriff, z. gleichzeitige Iteratoren.

4
starblue

Es gibt ein kritisches Merkmal zu beachten, dass ConcurrentHashMap nicht die Parallelitätsfunktion bietet, die ausfallsicher iterator ist. Ich habe Entwickler gesehen, die ConcurrentHashMap verwenden, nur weil sie das Entryset bearbeiten möchten - put/remove, während ich darüber iteriere. Collections.synchronizedMap(Map) bietet keinen fail-safe iterator, aber es liefert ausfallsicher stattdessen Iterator. Fail-Fast-Iteratoren verwenden eine Momentaufnahme der Kartengröße, die während der Iteration nicht bearbeitet werden kann.

3
hi.nitish
  1. Wenn Datenkonsistenz sehr wichtig ist - Verwenden Sie Hashtable oder Collections.synchronizedMap (Map).
  2. Wenn Geschwindigkeit und Leistung von großer Bedeutung sind und die Aktualisierung von Daten beeinträchtigt werden kann, verwenden Sie ConcurrentHashMap.
3
Shivam Maharshi

Die Collections.synchronizedMap () -Methode synchronisiert alle Methoden der HashMap und reduziert sie effektiv auf eine Datenstruktur, in die jeweils ein Thread eintreten kann, da jede Methode für eine gemeinsame Sperre gesperrt wird. 

In ConcurrentHashMap erfolgt die Synchronisierung etwas anders. Anstatt jede Methode für eine gemeinsame Sperre zu sperren, verwendet ConcurrentHashMap eine separate Sperre für separate Buckets, wodurch nur ein Teil der Map gesperrt wird. Standardmäßig gibt es 16 Buckets und separate Sperren für separate Buckets. Die standardmäßige Parallelitätsebene ist also 16. Dies bedeutet, dass theoretisch 16 Threads theoretisch auf ConcurrentHashMap zugreifen können, wenn sie alle Buckets trennen.

1
infoj

Wenn Sie ConcurrentHashMap verwenden möchten, stellen Sie sicher, dass Sie bereit sind, Aktualisierungen zu verpassen.
(Das Drucken von Inhalten der HashMap stellt nicht sicher, dass die aktuelle Map gedruckt wird.) und verwendet APIs wie CyclicBarrier, um die Konsistenz über den gesamten Lebenszyklus Ihres Programms sicherzustellen.

1
Kounavi

Neben dem vorgeschlagenen Thema möchte ich auch den Quellcode zu SynchronizedMap veröffentlichen.

Um einen Map-Thread sicher zu machen, können wir die Collections.synchronizedMap-Anweisung verwenden und die Karteninstanz als Parameter eingeben.

Die Implementierung von synchronizedMap in Collections ist wie folgt 

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

Wie Sie sehen, wird das Eingabeobjekt Map vom Objekt SynchronizedMap umschlossen.
Lassen Sie uns in die Implementierung von SynchronizedMap eintauchen, 

 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();}
        }
    }

Was SynchronizedMap tut, kann als eine einzige Sperre für die primäre Methode des Eingabeobjekts Map hinzugefügt werden. Auf alle von der Sperre überwachten Methoden können nicht mehrere Threads gleichzeitig zugreifen. Das bedeutet, dass normale Operationen wie put und get von einem einzigen Thread gleichzeitig für alle Daten im Map-Objekt ausgeführt werden können. 

Der Map-Objektthread ist jetzt sicher, aber die Leistung kann in einigen Szenarien zu einem Problem werden. 

Die ConcurrentMap ist in der Implementierung viel komplizierter, wir können auf Erstellen einer besseren HashMap für Details verweisen. Kurz gesagt: Es wurde unter Berücksichtigung von Thread-Sicherheit und Leistung implementiert. 

0
Gearon