Guide du ConcurrentSkipListMap

Guide de la ConcurrentSkipListMap

1. Vue d'ensemble

Dans cet article rapide, nous allons examiner la classeConcurrentSkipListMap du packagejava.util.concurrent.

Cette construction nous permet de créer une logique thread-safe de manière sécurisée. Il est idéal pour les problèmes lorsque nous voulons créer un instantané immuable des données alors que d'autres threads insèrent encore des données dans la carte.

Nous allons résoudre un problème desorting a stream of events and getting a snapshot of the events that arrived in the last 60 seconds using that construct.

2. Logique de tri de flux

Supposons que nous ayons un flux d’événements provenant en permanence de plusieurs threads. Nous devons pouvoir prendre des événements des 60 dernières secondes, ainsi que des événements de plus de 60 secondes.

Tout d'abord, définissons la structure de nos données d'événement:

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

    // standard constructors/getters
}

Nous voulons garder nos événements triés en utilisant le champeventTime. Pour y parvenir en utilisant lesConcurrentSkipListMap,, nous devons passer unComparator à son constructeur lors de la création d'une instance de celui-ci:

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

Nous comparerons tous les événements arrivés en utilisant leurs horodatages. Nous utilisons la méthodecomparingLong() et transmettons la fonction d'extraction qui peut prendre un horodatagelong duZonedDateTime.

Lorsque nos événements arrivent, il suffit de les ajouter à la carte en utilisant la méthodeput(). Notez que cette méthode ne nécessite aucune synchronisation explicite:

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

LeConcurrentSkipListMap gérera le tri de ces événements en dessous en utilisant leComparator qui lui a été passé dans le constructeur.

Les avantages les plus notables desConcurrentSkipListMap sont les méthodes qui peuvent créer un instantané immuable de ses données sans verrouillage. Pour obtenir tous les événements arrivés au cours de la dernière minute, nous pouvons utiliser la méthodetailMap() et passer l'heure à partir de laquelle nous voulons obtenir les éléments:

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

Il renverra tous les événements de la dernière minute. Ce sera un instantané immuable et le plus important est que les autres threads d'écriture peuvent ajouter de nouveaux événements auxConcurrentSkipListMap sans avoir besoin de faire un verrouillage explicite.

Nous pouvons maintenant obtenir tous les événements qui sont arrivés plus tard dans une minute - en utilisant la méthodeheadMap():

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

Cela retournera un instantané immuable de tous les événements de plus d'une minute. Toutes les méthodes ci-dessus appartiennent à la classeEventWindowSort, que nous utiliserons dans la section suivante.

3. Test de la logique du flux de tri

Une fois que nous avons implémenté notre logique de tri en utilisant lesConcurrentSkipListMap,, nous pouvons maintenanttest it by creating two writer threads qui enverront cent événements chacun:

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

Chaque thread appelle la méthodeacceptEvent(), envoyant les événements qui onteventTime à partir de maintenant à «maintenant moins cent secondes».

En attendant, nous pouvons invoquer la méthodegetEventsFromLastMinute() qui retournera l'instantané des événements qui se trouvent dans la fenêtre d'une minute:

ConcurrentNavigableMap eventsFromLastMinute
  = eventWindowSort.getEventsFromLastMinute();

Le nombre d'événements dans leseventsFromLastMinute variera à chaque test en fonction de la vitesse à laquelle les threads producteurs enverront les événements auxEventWindowSort. Nous pouvons affirmer qu'il n'y a pas un seul événement dans l'instantané renvoyé datant de plus d'une minute:

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

assertEquals(eventsOlderThanOneMinute, 0);

Et qu'il y a plus que zéro événements dans l'instantané qui se trouvent dans la fenêtre minute:

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

assertTrue(eventYoungerThanOneMinute > 0);

NotregetEventsFromLastMinute() utilise lestailMap() en dessous.

Testons maintenant lesgetEventsOlderThatOneMinute() qui utilisent la méthodeheadMap() à partir desConcurrentSkipListMap:

ConcurrentNavigableMap eventsFromLastMinute
  = eventWindowSort.getEventsOlderThatOneMinute();

Cette fois, nous obtenons un instantané des événements de plus d'une minute. Nous pouvons affirmer qu'il y a plus de zéro d'événements de ce type:

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

assertTrue(eventsOlderThanOneMinute > 0);

Et ensuite, qu'il n'y a pas un seul événement qui se situe dans la dernière minute:

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

assertEquals(eventYoungerThanOneMinute, 0);

La chose la plus importante à noter est quewe can take the snapshot of data while other threads are still adding new values auxConcurrentSkipListMap.

4. Conclusion

Dans ce rapide tutoriel, nous avons examiné les bases desConcurrentSkipListMap, ainsi que quelques exemples pratiques.

Nous avons exploité les hautes performances desConcurrentSkipListMap pour implémenter un algorithme non bloquant qui peut nous servir un instantané immuable des données même si en même temps plusieurs threads mettent à jour la carte.

L'implémentation de tous ces exemples et extraits de code peut être trouvée dans lesGitHub project; il s'agit d'un projet Maven, il devrait donc être facile à importer et à exécuter tel quel.