Sprievodca po ConcurrentSkipListMap

1. Prehľad

V tomto rýchlom článku sa pozrieme na ConcurrentSkipListMap triedy z java.util.concurrent balíček.

Táto konštrukcia nám umožňuje vytvárať logiku bezpečnú pre vlákna bez zámkov. Je to ideálne riešenie pre problémy, keď chceme vytvoriť nemennú snímku údajov, zatiaľ čo iné vlákna stále vkladajú údaje do mapy.

Budeme riešiť problém triedenie toku udalostí a získanie snímky udalostí, ktoré prišli za posledných 60 sekúnd pomocou tohto konštruktu.

2. Logika triedenia streamu

Povedzme, že máme sled udalostí, ktoré neustále pochádzajú z viacerých vlákien. Musíme byť schopní brať udalosti z posledných 60 sekúnd a tiež udalosti staršie ako 60 sekúnd.

Najprv definujeme štruktúru našich údajov o udalostiach:

verejná trieda Udalosť {private ZonedDateTime eventTime; súkromný obsah reťazca; // štandardné konštruktory / getre}

Chceme, aby naše udalosti boli triedené pomocou eventTime lúka. Aby ste to dosiahli pomocou ConcurrentSkipListMap, musíme zložiť a Komparátor jeho konštruktorovi pri vytváraní jeho inštancie:

ConcurrentSkipListMap events = nový ConcurrentSkipListMap (Comparator.comparingLong (v -> v.toInstant (). ToEpochMilli ()));

Budeme porovnávať všetky prichádzajúce udalosti pomocou ich časových značiek. Používame porovnanieLong () metóda a odovzdanie funkcie extraktu, ktorá môže trvať a dlho časová známka z ZonedDateTime.

Keď prídu naše udalosti, stačí ich pridať na mapu pomocou put () metóda. Upozorňujeme, že táto metóda nevyžaduje žiadnu explicitnú synchronizáciu:

public void acceptEvent (udalosť udalosti) {events.put (event.getEventTime (), event.getContent ()); }

The ConcurrentSkipListMap spracuje triedenie týchto udalostí pod pomocou Komparátor to mu bolo odovzdané v konštruktore.

Najvýznamnejšie klady ConcurrentSkipListMap sú metódy, pomocou ktorých je možné vytvoriť nemenný prehľad jeho údajov bezbariérovým spôsobom. Na získanie všetkých udalostí, ktoré prišli za poslednú minútu, môžeme použiť tailMap () metóda a čas, od ktorého chceme získať prvky:

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

Vráti všetky udalosti z poslednej minúty. Bude to nemenná snímka a najdôležitejšie je, že iné vlákna na písanie môžu do udalosti pridať nové udalosti ConcurrentSkipListMap bez nutnosti výslovného zamykania.

Teraz môžeme získať všetky udalosti, ktoré prišli neskôr o jednu minútu - pomocou headMap () metóda:

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

Takto sa vráti nezmeniteľný prehľad všetkých udalostí starších ako jedna minúta. Všetky vyššie uvedené metódy patria do EventWindowSort triedy, ktorú použijeme v ďalšej časti.

3. Testovanie logiky toku triedenia

Akonáhle sme implementovali našu logiku triedenia pomocou ConcurrentSkipListMap, môžeme teraz otestujte to vytvorením dvoch vláken spisovateľa ktoré pošlú každú udalosť sto:

ExecutorService executorService = Executors.newFixedThreadPool (3); EventWindowSort eventWindowSort = nový EventWindowSort (); int numberOfThreads = 2; Spustiteľný producent = () -> IntStream .rangeClosed (0, 100) .forEach (index -> eventWindowSort.acceptEvent (nová udalosť (ZonedDateTime.now (). MinusSeconds (index), UUID.randomUUID (). ToString ()))) ); pre (int i = 0; i <numberOfThreads; i ++) {executorService.execute (producent); } 

Každé vlákno vyvoláva acceptEvent () metódou, odosielanie udalostí, ktoré majú eventTime odteraz do „teraz mínus sto sekúnd“.

Medzitým môžeme vyvolať getEventsFromLastMinute () metóda, ktorá vráti prehľad udalostí, ktoré sú v rámci minútového okna:

ConcurrentNavigableMap eventsFromLastMinute = eventWindowSort.getEventsFromLastMinute ();

Počet udalostí v eventsFromLastMinute sa bude líšiť v každej testovacej prevádzke v závislosti od rýchlosti, s akou budú vlákna výrobcu posielať udalosti do EventWindowSort. Môžeme tvrdiť, že na vrátenej snímke nie je ani jedna udalosť staršia ako jedna minúta:

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

A že v snímke je viac ako nula udalostí, ktoré sa nachádzajú v okne jednej minúty:

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

Náš getEventsFromLastMinute () používa tailMap () zospodu.

Poďme teraz otestovať getEventsOlderThatOneMinute () ktorý používa headMap () metóda z ConcurrentSkipListMap:

ConcurrentNavigableMap eventsFromLastMinute = eventWindowSort.getEventsOlderThatOneMinute ();

Tentokrát získame prehľad udalostí starších ako jedna minúta. Môžeme tvrdiť, že takýchto udalostí je viac ako nula:

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

A ďalej, že neexistuje žiadna udalosť, ktorá by bola z poslednej minúty:

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

Najdôležitejšie je uvedomiť si, že môžeme urobiť snímku údajov, zatiaľ čo iné vlákna stále pridávajú nové hodnoty do ConcurrentSkipListMap.

4. Záver

V tomto rýchlom výučbe sme sa pozreli na základy ConcurrentSkipListMapspolu s niekoľkými praktickými príkladmi.

Využili sme vysoký výkon ConcurrentSkipListMap implementovať neblokujúci algoritmus, ktorý nám môže poskytnúť nemennú snímku údajov, aj keď mapu aktualizuje súčasne viac vlákien.

Implementáciu všetkých týchto príkladov a útržkov kódu nájdete v projekte GitHub; toto je projekt Maven, takže by malo byť ľahké ho importovať a spustiť tak, ako je.


$config[zx-auto] not found$config[zx-overlay] not found