it-swarm.com.de

Verschiedene Arten von Thread-sicheren Sets in Java

Es scheint viele verschiedene Implementierungen und Methoden zu geben, um Thread-sichere Sets in Java ..__ zu generieren. Einige Beispiele enthalten

1) CopyOnWriteArraySet

2) Collections.synchronizedSet (Set gesetzt) ​​

3) ConcurrentSkipListSet

4) Collections.newSetFromMap (neue ConcurrentHashMap ())

5) Andere Sätze, die ähnlich wie (4) erzeugt wurden 

Diese Beispiele stammen aus Concurrency Pattern: Concurrent Set-Implementierungen in Java 6

Könnte jemand bitte einfach die Unterschiede, Vorteile und Nachteile dieser und anderer Beispiele erklären? Ich habe Probleme, alles von den Java Std Docs zu verstehen.

113
Ben

1) Die Variable CopyOnWriteArraySet ist eine recht einfache Implementierung. Sie enthält im Wesentlichen eine Liste von Elementen in einem Array. Wenn Sie die Liste ändern, wird das Array kopiert. Iterationen und andere Zugriffe, die zu diesem Zeitpunkt ausgeführt werden, werden mit dem alten Array fortgesetzt, sodass keine Synchronisierung zwischen Lesern und Schreibern erforderlich ist (obwohl das Schreiben selbst synchronisiert werden muss). Die normalerweise schnellen Set-Operationen (insbesondere contains()) sind hier ziemlich langsam, da die Arrays in linearer Zeit durchsucht werden.

Verwenden Sie dies nur für wirklich kleine Sets, die oft gelesen (iteriert) und selten geändert werden. (Swings-Listener-Sets sind ein Beispiel, aber diese sind nicht wirklich Sets und sollten sowieso nur vom EDT verwendet werden.)

2) Collections.synchronizedSet umgibt einfach jede Methode des ursprünglichen Satzes mit einem Synchronisationsblock. Sie sollten nicht direkt auf das Originalset zugreifen. Dies bedeutet, dass keine zwei Methoden des Sets gleichzeitig ausgeführt werden können (eine wird blockiert, bis die andere beendet ist). Dies ist Thread-sicher. Sie haben jedoch keine Parallelität, wenn tatsächlich mehrere Threads das Set verwenden. Wenn Sie den Iterator verwenden, müssen Sie normalerweise immer noch extern synchronisieren, um ConcurrentModificationExceptions zu vermeiden, wenn Sie den Satz zwischen Iteratoraufrufen ändern. Die Leistung entspricht der Leistung des Originalsatzes (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/Wiederholen, wobei die Iteration möglicherweise über Änderungen informiert wird, seit der Iterator erstellt wurde. Bei den Massenvorgängen handelt es sich einfach um mehrere Einzelaufrufe und nicht um atomare Aufrufe - andere Threads beobachten möglicherweise nur einige davon.

Offensichtlich können Sie dies nur verwenden, wenn Sie eine gewisse Gesamtreihenfolge für Ihre Elemente haben .. Dies scheint ein idealer Kandidat für Situationen mit hoher Parallelität zu sein, für nicht allzu große Mengen (wegen des O (log n)).

4) Für die ConcurrentHashMap (und das daraus abgeleitete Set): Hier sind die meisten grundlegenden Optionen (im Durchschnitt, wenn Sie eine gute und schnelle hashCode() haben) in O(1) (können jedoch zu O (n) degenerieren.) ), wie bei HashMap/HashSet. Es gibt eine begrenzte Parallelität für das Schreiben (die Tabelle ist partitioniert, und der Schreibzugriff wird auf der erforderlichen Partition synchronisiert), während der Lesezugriff vollständig mit sich selbst und den Schreibthreads übereinstimmt (die Ergebnisse der aktuellen Änderungen werden jedoch möglicherweise noch nicht angezeigt.) geschrieben). Der Iterator kann Änderungen sehen oder nicht sehen, seit er erstellt wurde, und Massenoperationen sind nicht atomar. Die Größenänderung ist langsam (wie bei HashMap/HashSet). Versuchen Sie dies zu vermeiden, indem Sie die erforderliche Größe beim Erstellen (und bei Verwendung von about) schätzen 1/3 mehr davon, da die Größenänderung zu 3/4 voll ist).

Verwenden Sie diese Option, wenn Sie große Mengen und eine gute (und schnelle) Hash-Funktion haben und vor dem Erstellen der Karte die eingestellte Größe und die erforderliche Parallelität abschätzen können.

5) Gibt es andere gleichzeitige Kartenimplementierungen, die hier verwendet werden könnten?

184
Paŭlo Ebermann

Es ist möglich, die contains()-Leistung von HashSet mit den gleichzeitigen Eigenschaften von CopyOnWriteArraySet zu kombinieren, indem Sie den AtomicReference<Set> verwenden und den gesamten Satz bei jeder Änderung ersetzen.

Die Implementierungsskizze:

public abstract class CopyOnWriteSet<E> implements Set<E> {

    private final AtomicReference<Set<E>> ref;

    protected CopyOnWriteSet( Collection<? extends E> c ) {
        ref = new AtomicReference<Set<E>>( new HashSet<E>( c ) );
    }

    @Override
    public boolean contains( Object o ) {
        return ref.get().contains( o );
    }

    @Override
    public boolean add( E e ) {
        while ( true ) {
            Set<E> current = ref.get();
            if ( current.contains( e ) ) {
                return false;
            }
            Set<E> modified = new HashSet<E>( current );
            modified.add( e );
            if ( ref.compareAndSet( current, modified ) ) {
                return true;
            }
        }
    }

    @Override
    public boolean remove( Object o ) {
        while ( true ) {
            Set<E> current = ref.get();
            if ( !current.contains( o ) ) {
                return false;
            }
            Set<E> modified = new HashSet<E>( current );
            modified.remove( o );
            if ( ref.compareAndSet( current, modified ) ) {
                return true;
            }
        }
    }

}
19
Oleg Estekhin

Wenn die Javadocs nicht helfen, sollten Sie wahrscheinlich nur ein Buch oder einen Artikel zum Lesen von Datenstrukturen finden. Auf einen Blick:

  • CopyOnWriteArraySet erstellt jedes Mal, wenn Sie die Auflistung mutieren, eine neue Kopie des zugrunde liegenden Arrays. Das Schreiben ist also langsam und die Iteratoren sind schnell und konsistent.
  • Collections.synchronizedSet () verwendet synchronisierte Methodenaufrufe der alten Schule, um ein Set threadsicher zu machen. Dies wäre eine Version mit geringer Leistung.
  • ConcurrentSkipListSet bietet performante Schreibvorgänge mit inkonsistenten Stapeloperationen (addAll, removeAll usw.) und Iteratoren.
  • Collections.newSetFromMap (new ConcurrentHashMap ()) hat die Semantik von ConcurrentHashMap, die meines Erachtens nicht unbedingt für Lese- oder Schreibvorgänge optimiert ist, aber wie ConcurrentSkipListSet inkonsistente Stapeloperationen aufweist.
10
Ryan Stewart

Gleichzeitige Reihe von schwachen Referenzen

Eine weitere Variante ist ein Thread-sicherer Satz von schwachen Referenzen

Ein solches Set ist praktisch, um Abonnenten in einem pub-sub Szenario zu verfolgen. Wenn ein Abonnent an einem anderen Ort den Geltungsbereich verlässt und sich daher als Kandidat für die Müllabfuhr erweist, muss sich der Abonnent nicht um die Abmeldung gekümmert kümmern. Die schwache Referenz ermöglicht es dem Abonnenten, den Übergang zu einem Kandidaten für die Müllsammlung abzuschließen. Wenn der Müll schließlich gesammelt wird, wird der Eintrag im Satz entfernt.

Während mit den gebündelten Klassen kein solcher Satz direkt bereitgestellt wird, können Sie mit ein paar Aufrufen einen erstellen.

Zuerst beginnen wir mit der Erstellung einer Set von schwachen Referenzen, indem wir die WeakHashMap -Klasse nutzen. Dies wird in der Klassendokumentation für Collections.newSetFromMap gezeigt.

Set< YourClassGoesHere > weakHashSet = 
    Collections
    .newSetFromMap(
        new WeakHashMap< YourClassGoesHere , Boolean >()
    )
;

Der Value der Karte, Boolean, ist hier irrelevant, da der Key der Karte unsere Set ausmacht. 

In einem Szenario wie pub-sub benötigen wir Threadsicherheit, wenn die Abonnenten und Publisher mit separaten Threads arbeiten (dies ist sehr wahrscheinlich der Fall). 

Gehen Sie noch einen Schritt weiter, indem Sie als synchronisierte Gruppe umschließen, um diese Gruppe threadsicher zu machen. Geben Sie einen Anruf an Collections.synchronizedSet ein.

this.subscribers =
        Collections.synchronizedSet(
                Collections.newSetFromMap(
                        new WeakHashMap <>()  // Parameterized types `< YourClassGoesHere , Boolean >` are inferred, no need to specify.
                )
        );

Jetzt können wir Abonnenten zu unserer resultierenden Set hinzufügen und daraus entfernen. Und alle "verschwindenden" Abonnenten werden nach der Ausführung der Speicherbereinigung automatisch entfernt. Wann diese Ausführung erfolgt, hängt von der Garbage-Collector-Implementierung Ihrer JVM und von der aktuellen Laufzeitsituation ab. Für Diskussionen und Beispiele, wann und wie die zugrunde liegende WeakHashMap die abgelaufenen Einträge löscht, siehe diese Frage, * Wächst WeakHashMap ständig oder werden die Müllschlüssel gelöscht? * .

0
Basil Bourque