1) Das CopyOnWriteArraySet
ist eine recht einfache Implementierung - es enthält im Grunde eine Liste von Elementen in einem Array, und beim Ändern der Liste wird das Array kopiert. Iterationen und andere Zugriffe, die zu diesem Zeitpunkt ausgeführt werden, werden mit dem alten Array fortgesetzt, wodurch die Notwendigkeit einer Synchronisierung zwischen Lesern und Schreibern vermieden wird (obwohl das Schreiben selbst synchronisiert werden muss). Die normalerweise schnell eingestellten Operationen (insbesondere contains()
) sind hier ziemlich langsam, da die Arrays in linearer Zeit durchsucht werden.
Verwenden Sie dies nur für wirklich kleine Mengen, die häufig gelesen (iteriert) und selten geändert werden. (Swings Listener-Sets wären ein Beispiel, aber dies sind keine wirklichen Sets und sollten sowieso nur vom EDT verwendet werden.)
2) Collections.synchronizedSet
wickelt einfach einen synchronisierten Block um jede Methode des ursprünglichen Satzes. Sie sollten nicht direkt auf das Originalset zugreifen. Dies bedeutet, dass keine zwei Methoden des Satzes gleichzeitig ausgeführt werden können (eine wird blockiert, bis die andere beendet ist) - dies ist threadsicher, aber Sie haben keine Parallelität, wenn mehrere Threads den Satz tatsächlich verwenden. Wenn Sie den Iterator verwenden, müssen Sie normalerweise noch extern synchronisieren, um ConcurrentModificationExceptions zu vermeiden, wenn Sie den Satz zwischen Iteratoraufrufen ändern. Die Leistung entspricht der Leistung des ursprünglichen Satzes (jedoch mit etwas Synchronisationsaufwand und Blockierung bei gleichzeitiger Verwendung).
Verwenden Sie diese Option, wenn Sie nur eine geringe Parallelität haben und sicherstellen möchten, dass alle Änderungen für die anderen Threads sofort sichtbar sind.
3) ConcurrentSkipListSet
ist die gleichzeitige SortedSet
Implementierung mit den meisten grundlegenden Operationen in O (log n). Es ermöglicht das gleichzeitige Hinzufügen / Entfernen und Lesen / Iterieren, wobei die Iteration möglicherweise über Änderungen seit der Erstellung des Iterators informiert oder nicht. Die Massenoperationen sind einfach mehrere Einzelaufrufe und nicht atomar - andere Threads beobachten möglicherweise nur einige von ihnen.
Natürlich können Sie dies nur verwenden, wenn Sie eine Gesamtreihenfolge für Ihre Elemente haben. Dies scheint ein idealer Kandidat für Situationen mit hoher Parallelität zu sein, für nicht zu große Mengen (aufgrund des O (log n)).
4) Für die ConcurrentHashMap
(und die daraus abgeleitete Menge): Hier sind die meisten grundlegenden Optionen (im Durchschnitt, wenn Sie eine gute und schnelle haben hashCode()
) in O (1) (können aber zu O (n) degenerieren), wie für HashMap / HashSet. Es gibt eine begrenzte Parallelität zum Schreiben (die Tabelle ist partitioniert und der Schreibzugriff wird auf der erforderlichen Partition synchronisiert), während der Lesezugriff vollständig gleichzeitig mit sich selbst und den Schreibthreads erfolgt (die Ergebnisse der aktuellen Änderungen werden jedoch möglicherweise noch nicht angezeigt geschrieben). Der Iterator kann Änderungen seit seiner Erstellung sehen oder nicht, und Massenoperationen sind nicht atomar. Die Größenänderung ist langsam (wie bei HashMap / HashSet). Versuchen Sie daher, dies zu vermeiden, indem Sie die erforderliche Größe bei der Erstellung schätzen (und etwa 1/3 mehr davon verwenden, da die Größe geändert wird, wenn 3/4 voll ist).
Verwenden Sie diese Option, wenn Sie große Mengen und eine gute (und schnelle) Hash-Funktion haben und die Satzgröße und die erforderliche Parallelität vor dem Erstellen der Karte schätzen können.
5) Gibt es andere gleichzeitige Kartenimplementierungen, die hier verwendet werden könnten?
ConcurrentHashMap
Versuchen Sie daher, dies auf der Grundlage der Menge zu vermeiden, indem Sie die erforderliche Größe bei der Erstellung schätzen. Die Größe, die Sie der Karte geben, sollte über 33% größer sein als Ihre Schätzung (oder Ihr bekannter Wert), da die Größe des Sets bei 75% Last geändert wird. Ich benutzeexpectedSize + 4 / 3 + 1
+
soll ein sein*
?expectedSize * 4 / 3 + 1
ConcurrentMap
(oderHashMap
) in Java 8, wenn die Anzahl der Einträge, die demselben Bucket zugeordnet sind, den Schwellenwert erreicht (ich glaube, es ist 16), wird die Liste in einen binären Suchbaum geändert (rot-schwarzer Baum muss präzisiert werden) und in diesem Fall nachschlagen Zeit wäreO(lg n)
und nichtO(n)
.Es ist möglich, die
contains()
Leistung vonHashSet
mit den parallelen Eigenschaften von zu kombinieren, indem bei jeder Änderung der gesamte SatzCopyOnWriteArraySet
verwendetAtomicReference<Set>
und ersetzt wird.Die Implementierungsskizze:
quelle
AtomicReference
den Wert flüchtig. Dies bedeutet, dass sichergestellt wird, dass kein Thread veraltete Daten ausliest, und dass dieshappens-before
garantiert, da der Code vom Compiler nicht neu angeordnet werden kann. Wenn jedoch nur get / set-Methoden vonAtomicReference
verwendet werden, markieren wir unsere Variable tatsächlich auf ausgefallene Weise als flüchtig.abstract
, anscheinend um zu vermeiden, dass ich mehrere Methoden schreiben muss. Ich machte mich daran, sie hinzuzufügen, stieß aber mit auf eine Straßensperreiterator()
. Ich weiß nicht, wie ich einen Iterator über diese Sache aufrechterhalten soll, ohne das Modell zu beschädigen. Scheint, als müsste ich immer die durchlaufenref
und könnte jedes Mal einen anderen zugrunde liegenden Satz erhalten, was erfordert, dass ein neuer Iterator für den zugrunde liegenden Satz erstellt wird, was für mich nutzlos ist, da er mit Element Null beginnt. Irgendwelche Einsichten?Wenn die Javadocs nicht helfen, sollten Sie wahrscheinlich nur ein Buch oder einen Artikel finden, um über Datenstrukturen zu lesen. Auf einen Blick:
quelle
Gleichzeitiger Satz schwacher Referenzen
Eine weitere Wendung ist ein fadensicherer Satz schwacher Referenzen .
Ein solches Set ist praktisch, um Abonnenten in einem Pub-Sub- Szenario zu verfolgen . Wenn ein Abonnent an anderen Orten den Geltungsbereich verlässt und daher auf dem Weg ist, ein Kandidat für die Speicherbereinigung zu werden, muss der Abonnent nicht die Mühe haben, sich ordnungsgemäß abzumelden. Die schwache Referenz ermöglicht es dem Abonnenten, seinen Übergang zum Kandidaten für die Speicherbereinigung abzuschließen. Wenn der Müll schließlich gesammelt wird, wird der Eintrag im Set entfernt.
Während ein solcher Satz nicht direkt mit den gebündelten Klassen bereitgestellt wird, können Sie mit wenigen Aufrufen einen erstellen.
Zuerst beginnen wir damit,
Set
schwache Referenzen zu erstellen, indem wir dieWeakHashMap
Klasse nutzen. Dies wird in der Klassendokumentation für gezeigtCollections.newSetFromMap
.Der Wert der Karte
Boolean
ist hier irrelevant, da der Schlüssel der Karte unsere istSet
.In einem Szenario wie pub-sub benötigen wir Thread-Sicherheit, wenn die Abonnenten und Herausgeber auf separaten Threads arbeiten (sehr wahrscheinlich).
Gehen Sie noch einen Schritt weiter, indem Sie als synchronisierten Satz einwickeln, um diesen Satz threadsicher zu machen. In einen Anruf einspeisen
Collections.synchronizedSet
.Jetzt können wir Abonnenten zu unseren Ergebnissen hinzufügen und daraus entfernen
Set
. Und alle "verschwindenden" Abonnenten werden schließlich automatisch entfernt, nachdem die Speicherbereinigung ausgeführt wurde. Wann diese Ausführung erfolgt, hängt von der Garbage-Collector-Implementierung Ihrer JVM und von der aktuellen Laufzeitsituation ab. Eine Diskussion und ein Beispiel dafür, wann und wie der BasiswertWeakHashMap
die abgelaufenen Einträge löscht, finden Sie in dieser Frage: * Wächst WeakHashMap ständig oder werden die Müllschlüssel gelöscht? * .quelle