Eine Einführung in synchronisierte Java-Sammlungen

Eine Einführung in synchronisierte Java-Sammlungen

1. Überblick

collections framework ist eine Schlüsselkomponente von Java. Es bietet eine Vielzahl von Schnittstellen und Implementierungen, mit denen wir verschiedene Arten von Sammlungen auf einfache Weise erstellen und bearbeiten können.

Die Verwendung von nicht synchronisierten Auflistungen ist zwar insgesamt einfach, kann jedoch bei der Arbeit in Umgebungen mit mehreren Threads zu einem entmutigenden und fehleranfälligen Prozess werden (a.k.a. gleichzeitige Programmierung).

Daher bietet die Java-Plattform eine starke Unterstützung für dieses Szenario durch verschiedene Synchronisationswrappers, die in der KlasseCollectionsimplementiert sind.

Mit diesen Wrappern können mithilfe mehrerer statischer Factory-Methoden auf einfache Weise synchronisierte Ansichten der gelieferten Sammlungen erstellt werden.

In diesem Tutorial werdenwe’ll take a deep dive into these *static synchronization wrappers. Also, we’ll highlight the difference between synchronized collections and concurrent collections. *

2. DiesynchronizedCollection()-Methode

Der erste Synchronisations-Wrapper, den wir in dieser Zusammenfassung behandeln, ist diesynchronizedCollection()-Methode. Wie der Name schon sagt,it returns a thread-safe collection backed up by the specified Collection.

Um besser zu verstehen, wie diese Methode verwendet wird, erstellen wir einen grundlegenden Komponententest:

Collection syncCollection = Collections.synchronizedCollection(new ArrayList<>());
    Runnable listOperations = () -> {
        syncCollection.addAll(Arrays.asList(1, 2, 3, 4, 5, 6));
    };

    Thread thread1 = new Thread(listOperations);
    Thread thread2 = new Thread(listOperations);
    thread1.start();
    thread2.start();
    thread1.join();
    thread2.join();

    assertThat(syncCollection.size()).isEqualTo(12);
}

Wie oben gezeigt, ist das Erstellen einer synchronisierten Ansicht der gelieferten Sammlung mit dieser Methode sehr einfach.

Um zu demonstrieren, dass die Methode tatsächlich eine thread-sichere Auflistung zurückgibt, erstellen wir zunächst einige Threads.

Danach injizieren wir eineRunnable-Instanz in Form eines Lambda-Ausdrucks in ihre Konstruktoren. Beachten Sie, dassRunnable eine funktionale Schnittstelle ist, sodass wir sie durch einen Lambda-Ausdruck ersetzen können.

Zuletzt überprüfen wir nur, ob jeder Thread der synchronisierten Auflistung effektiv sechs Elemente hinzufügt, sodass seine endgültige Größe zwölf beträgt.

3. DiesynchronizedList()-Methode

Ähnlich wie bei dersynchronizedCollection()-Methode können wir auch densynchronizedList()-Wrapper verwenden, um synchronisierteList zu erstellen.

Wie zu erwarten,the method returns a thread-safe view of the specified List:

List syncList = Collections.synchronizedList(new ArrayList<>());

Es ist nicht überraschend, dass die Verwendung der MethodesynchronizedList() nahezu identisch mit der übergeordneten MethodesynchronizedCollection() ist.

Daher können wir, wie wir es gerade im vorherigen Komponententest getan haben, nach dem Erstellen eines synchronisiertenList mehrere Threads erzeugen. Danach verwenden wir sie, um threadsicher auf das ZielList zuzugreifen / es zu bearbeiten.

Wenn wir über eine synchronisierte Auflistung iterieren und unerwartete Ergebnisse verhindern möchten, sollten wir darüber hinaus ausdrücklich unsere eigene thread-sichere Implementierung der Schleife bereitstellen. Daher könnten wir dies mit einemsynchronized-Block erreichen:

List syncCollection = Collections.synchronizedList(Arrays.asList("a", "b", "c"));
List uppercasedCollection = new ArrayList<>();

Runnable listOperations = () -> {
    synchronized (syncCollection) {
        syncCollection.forEach((e) -> {
            uppercasedCollection.add(e.toUpperCase());
        });
    }
};

In allen Fällen, in denen wir über eine synchronisierte Auflistung iterieren müssen, sollten wir dieses Idiom implementieren. Dies liegt daran, dass die Iteration einer synchronisierten Sammlung durch mehrere Aufrufe in der Sammlung ausgeführt wird. Daher müssen sie als einzelne atomare Operation ausgeführt werden.

The use of the synchronized block ensures the atomicity of the operation.

4. DiesynchronizedMap()-Methode

DieCollections-Klasse implementiert einen anderen übersichtlichen Synchronisations-Wrapper namenssynchronizedMap().. Wir könnten ihn zum einfachen Erstellen eines synchronisiertenMap verwenden.

The method returns a thread-safe view of the supplied Map implementation:

Map syncMap = Collections.synchronizedMap(new HashMap<>());

5. DiesynchronizedSortedMap()-Methode

Es gibt auch eine Gegenimplementierung dersynchronizedMap()-Methode. Es heißtsynchronizedSortedMap(), mit dem wir eine synchronisierteSortedMap-Instanz erstellen können:

Map syncSortedMap = Collections.synchronizedSortedMap(new TreeMap<>());

6. DiesynchronizedSet()-Methode

Als nächstes haben wir in dieser Übersicht die MethodesynchronizedSet(). Wie der Name schon sagt, können wir mit minimalem Aufwand synchronisierteSets erstellen.

The wrapper returns a thread-safe collection backed by the specified Set:

Set syncSet = Collections.synchronizedSet(new HashSet<>());

7. DiesynchronizedSortedSet()-Methode

Der letzte Synchronisations-Wrapper, den wir hier vorstellen, istsynchronizedSortedSet().

Ähnlich wie bei anderen Wrapper-Implementierungen, die wir bisher überprüft haben, sindthe method returns a thread-safe version of the given SortedSet:

SortedSet syncSortedSet = Collections.synchronizedSortedSet(new TreeSet<>());

8. Synchronisierte und gleichzeitige Sammlungen

Bis zu diesem Punkt haben wir uns die Synchronisations-Wrapper des Sammlungsframeworks genauer angesehen.

Konzentrieren wir uns nun auf the differences between synchronized collections and concurrent collections, z. B. die Implementierungen vonConcurrentHashMap undBlockingQueue.

8.1. Synchronisierte Sammlungen

Synchronized collections achieve thread-safety through intrinsihttps://docs.oracle.com/javase/tutorial/essential/concurrency/locksync.html[c locking], and the entire collections are locked. Das intrinsische Sperren wird über synchronisierte Blöcke innerhalb der Methoden der umschlossenen Sammlung implementiert.

Wie zu erwarten, stellen synchronisierte Sammlungen die Datenkonsistenz und -integrität in Umgebungen mit mehreren Threads sicher. Es kann jedoch zu Leistungseinbußen kommen, da jeweils nur ein Thread auf die Sammlung zugreifen kann (a.k.a. synchronisierter Zugriff).

Eine ausführliche Anleitung zur Verwendung der Methoden und Blöcke vonsynchronizedfinden Sie unterour article zum Thema.

8.2. Gleichzeitige Sammlungen

Concurrent collections (e.g. ConcurrentHashMap), achieve thread-safety by dividing their data into segments. InConcurrentHashMap können beispielsweise verschiedene Threads Sperren für jedes Segment erwerben, sodass mehrere Threads gleichzeitig auf dieMap zugreifen können (a.k.a. gleichzeitiger Zugriff).

Gleichzeitige Sammlungen sindmuch more performant than synchronized collections, da der gleichzeitige Threadzugriff inhärente Vorteile bietet.

Die Auswahl des Typs der thread-sicheren Sammlung hängt also von den Anforderungen des jeweiligen Anwendungsfalls ab und sollte entsprechend bewertet werden.

9. Fazit

In this article, we took an in-depth look at the set of synchronization wrappers implemented within the Collections class.

Darüber hinaus haben wir die Unterschiede zwischen synchronisierten und gleichzeitigen Auflistungen hervorgehoben und die von ihnen implementierten Ansätze zum Erreichen der Threadsicherheit untersucht.

Wie üblich sind alle in diesem Artikel gezeigten Codebeispiele überGitHub verfügbar.