Leitfaden zur ConcurrentSkipListMap

Anleitung zur ConcurrentSkipListMap

1. Überblick

In diesem kurzen Artikel sehen wir uns die KlasseConcurrentSkipListMapaus dem Paketjava.util.concurrentan.

Mit diesem Konstrukt können wir eine thread-sichere Logik ohne Sperren erstellen. Es ist ideal für Probleme, wenn wir einen unveränderlichen Schnappschuss der Daten erstellen möchten, während andere Threads noch Daten in die Karte einfügen.

Wir werden ein Problem vonsorting a stream of events and getting a snapshot of the events that arrived in the last 60 seconds using that construct lösen.

2. Stream-Sortierlogik

Nehmen wir an, wir haben einen Strom von Ereignissen, die ständig von mehreren Threads ausgehen. Wir müssen in der Lage sein, Ereignisse der letzten 60 Sekunden und Ereignisse, die älter als 60 Sekunden sind, zu erfassen.

Definieren wir zunächst die Struktur unserer Ereignisdaten:

public class Event {
    private ZonedDateTime eventTime;
    private String content;

    // standard constructors/getters
}

Wir möchten unsere Ereignisse nach dem FeldeventTimeortieren. Um dies mitConcurrentSkipListMap, zu erreichen, müssen wir einComparator an seinen Konstruktor übergeben, während wir eine Instanz davon erstellen:

ConcurrentSkipListMap events
 = new ConcurrentSkipListMap<>(
 Comparator.comparingLong(v -> v.toInstant().toEpochMilli()));

Wir vergleichen alle eingetroffenen Ereignisse anhand ihrer Zeitstempel. Wir verwenden die MethodecomparingLong() und übergeben die Extraktionsfunktion, die einen Zeitstempel vonlongvonZonedDateTime. übernehmen kann

Wenn unsere Ereignisse eintreffen, müssen wir sie nur mit der Methodeput()zur Karte hinzufügen. Beachten Sie, dass für diese Methode keine explizite Synchronisierung erforderlich ist:

public void acceptEvent(Event event) {
    events.put(event.getEventTime(), event.getContent());
}

DieConcurrentSkipListMap übernehmen die Sortierung der darunter liegenden Ereignisse unter Verwendung derComparator, die im Konstruktor an sie übergeben wurden.

Die bemerkenswertesten Vorteile derConcurrentSkipListMap sind die Methoden, mit denen eine unveränderliche Momentaufnahme ihrer Daten auf sperrenfreie Weise erstellt werden kann. Um alle Ereignisse zu erhalten, die innerhalb der letzten Minute eingetroffen sind, können wir dietailMap()-Methode verwenden und die Zeit übergeben, ab der wir Elemente abrufen möchten:

public ConcurrentNavigableMap getEventsFromLastMinute() {
    return events.tailMap(ZonedDateTime.now().minusMinutes(1));
}

Es werden alle Ereignisse der letzten Minute zurückgegeben. Es wird ein unveränderlicher Schnappschuss sein, und das Wichtigste ist, dass andere Schreibthreads denConcurrentSkipListMap neue Ereignisse hinzufügen können, ohne dass eine explizite Sperrung erforderlich ist.

Wir können jetzt alle Ereignisse abrufen, die in einer Minute später eingetroffen sind - mithilfe der MethodeheadMap():

public ConcurrentNavigableMap getEventsOlderThatOneMinute() {
    return events.headMap(ZonedDateTime.now().minusMinutes(1));
}

Dies gibt eine unveränderliche Momentaufnahme aller Ereignisse zurück, die älter als eine Minute sind. Alle oben genannten Methoden gehören zur KlasseEventWindowSort, die wir im nächsten Abschnitt verwenden werden.

3. Testen der Sortierstromlogik

Sobald wir unsere Sortierlogik mitConcurrentSkipListMap, implementiert haben, können wir jetzttest it by creating two writer threads, die jeweils einhundert Ereignisse senden:

ExecutorService executorService = Executors.newFixedThreadPool(3);
EventWindowSort eventWindowSort = new EventWindowSort();
int numberOfThreads = 2;

Runnable producer = () -> IntStream
  .rangeClosed(0, 100)
  .forEach(index -> eventWindowSort.acceptEvent(
      new Event(ZonedDateTime.now().minusSeconds(index), UUID.randomUUID().toString()))
  );

for (int i = 0; i < numberOfThreads; i++) {
    executorService.execute(producer);
}

Jeder Thread ruft die MethodeacceptEvent() auf und sendet die Ereignisse miteventTime von jetzt an "jetzt minus hundert Sekunden".

In der Zwischenzeit können wir die MethodegetEventsFromLastMinute()aufrufen, die den Snapshot von Ereignissen zurückgibt, die sich innerhalb des Ein-Minuten-Fensters befinden:

ConcurrentNavigableMap eventsFromLastMinute
  = eventWindowSort.getEventsFromLastMinute();

Die Anzahl der Ereignisse ineventsFromLastMinute variiert in jedem Testlauf in Abhängigkeit von der Geschwindigkeit, mit der die Producer-Threads die Ereignisse anEventWindowSort. senden. Wir können behaupten, dass es kein einziges Ereignis in gibt Der zurückgegebene Schnappschuss, der älter als eine Minute ist:

long eventsOlderThanOneMinute = eventsFromLastMinute
  .entrySet()
  .stream()
  .filter(e -> e.getKey().isBefore(ZonedDateTime.now().minusMinutes(1)))
  .count();

assertEquals(eventsOlderThanOneMinute, 0);

Und dass der Snapshot mehr als null Ereignisse enthält, die sich innerhalb des Ein-Minuten-Fensters befinden:

long eventYoungerThanOneMinute = eventsFromLastMinute
  .entrySet()
  .stream()
  .filter(e -> e.getKey().isAfter(ZonedDateTime.now().minusMinutes(1)))
  .count();

assertTrue(eventYoungerThanOneMinute > 0);

UnseregetEventsFromLastMinute() verwenden dietailMap() darunter.

Testen wir nun diegetEventsOlderThatOneMinute(), die dieheadMap()-Methode verwenden, aus denConcurrentSkipListMap:

ConcurrentNavigableMap eventsFromLastMinute
  = eventWindowSort.getEventsOlderThatOneMinute();

Dieses Mal erhalten wir eine Momentaufnahme von Ereignissen, die älter als eine Minute sind. Wir können behaupten, dass es mehr als null solcher Ereignisse gibt:

long eventsOlderThanOneMinute = eventsFromLastMinute
  .entrySet()
  .stream()
  .filter(e -> e.getKey().isBefore(ZonedDateTime.now().minusMinutes(1)))
  .count();

assertTrue(eventsOlderThanOneMinute > 0);

Und als nächstes, dass es kein einziges Ereignis gibt, das aus der letzten Minute stammt:

long eventYoungerThanOneMinute = eventsFromLastMinute
  .entrySet()
  .stream()
  .filter(e -> e.getKey().isAfter(ZonedDateTime.now().minusMinutes(1)))
  .count();

assertEquals(eventYoungerThanOneMinute, 0);

Das Wichtigste ist, dasswe can take the snapshot of data while other threads are still adding new values zuConcurrentSkipListMap. gehört

4. Fazit

In diesem kurzen Tutorial haben wir uns die Grundlagen derConcurrentSkipListMap sowie einige praktische Beispiele. angesehen

Wir haben die hohe Leistung derConcurrentSkipListMap genutzt, um einen nicht blockierenden Algorithmus zu implementieren, der uns einen unveränderlichen Schnappschuss von Daten liefern kann, selbst wenn gleichzeitig mehrere Threads die Karte aktualisieren.

Die Implementierung all dieser Beispiele und Codefragmente finden Sie inGitHub project; Da es sich um ein Maven-Projekt handelt, sollte es einfach zu importieren und auszuführen sein.