Vodič za ConcurrentSkipListMap
1. Pregled
U ovom ćemo kratkom članku pogledati ConcurrentSkipListMap razred iz java.util.concurrent paket.
Ova nam konstrukcija omogućuje stvaranje logike sigurne u nitima na način bez zaključavanja. Idealno je za probleme kada želimo napraviti nepromjenjivu snimku podataka dok druge niti još uvijek ubacuju podatke na kartu.
Riješit ćemo problem sortiranje toka događaja i dobivanje snimke događaja koji su stigli u zadnjih 60 sekundi pomoću te konstrukcije.
2. Logika sortiranja toka
Recimo da imamo tok događaja koji neprestano dolaze iz više niti. Moramo biti u mogućnosti uzimati događaje u zadnjih 60 sekundi, kao i događaje starije od 60 sekundi.
Prvo definirajmo strukturu podataka o našem događaju:
javna klasa Event {private ZonedDateTime eventTime; privatni sadržaj niza; // standardni konstruktori / getteri}
Želimo da naši događaji budu sortirani pomoću eventTime polje. Da biste to postigli pomoću ConcurrentSkipListMap, trebamo proći a Usporednik njegovom konstruktoru dok stvara njegovu instancu:
ConcurrentSkipListMap događaji = novi ConcurrentSkipListMap (Comparator.comparingLong (v -> v.toInstant (). ToEpochMilli ()));
Usporedit ćemo sve pristigle događaje koristeći njihove vremenske oznake. Koristimo uspoređujućiLong () metoda i prosljeđivanje funkcije ekstrakcije koja može potrajati a dugo vremenska oznaka iz ZonedDateTime.
Kada naši događaji stignu, trebamo ih samo dodati na kartu pomoću staviti() metoda. Napominjemo da ova metoda ne zahtijeva izričitu sinkronizaciju:
javna praznina acceptEvent (događaj događaja) {events.put (event.getEventTime (), event.getContent ()); }
The ConcurrentSkipListMap će se nositi sa sortiranjem tih događaja ispod pomoću Usporednik koja mu je proslijeđena u konstruktoru.
Najistaknutiji profesionalci ConcurrentSkipListMap su metode koje mogu napraviti nepromjenjivu snimku svojih podataka na način bez zaključavanja. Da bismo dobili sve događaje koji su stigli u prošloj minuti, možemo koristiti tailMap () metodu i prođe vrijeme iz kojeg želimo dobiti elemente:
public ConcurrentNavigableMap getEventsFromLastMinute () {return events.tailMap (ZonedDateTime.now (). minusMinutes (1)); }
Vratit će sve događaje iz prošle minute. Bit će to nepromjenjiva snimka i najvažnije je da druge niti za pisanje mogu dodavati nove događaje u ConcurrentSkipListMap bez ikakve potrebe za eksplicitnim zaključavanjem.
Sada možemo dobiti sve događaje koji su stigli kasnije, nakon jedne minute - pomoću headMap () metoda:
public ConcurrentNavigableMap getEventsOlderThatOneMinute () {return events.headMap (ZonedDateTime.now (). minusMinutes (1)); }
To će vratiti nepromjenjivu snimku svih događaja starijih od jedne minute. Sve gore navedene metode pripadaju EventWindowSort razred, koji ćemo upotrijebiti u sljedećem odjeljku.
3. Testiranje logike toka sortiranja
Jednom kada smo implementirali našu logiku sortiranja pomoću ConcurrentSkipListMap, sada možemo testirajte ga stvaranjem dviju niti pisanja koji će poslati po sto događaja:
ExecutorService executorService = Izvršitelji.newFixedThreadPool (3); EventWindowSort eventWindowSort = novo EventWindowSort (); int numberOfThreads = 2; Izvodljivi proizvođač = () -> IntStream .rangeClosed (0, 100) .forEach (index -> eventWindowSort.acceptEvent (novi događaj (ZonedDateTime.now (). MinusSeconds (indeks), UUID.randomUUID (). ToString ())) ); for (int i = 0; i <numberOfThreads; i ++) {executorService.execute (producent); }
Svaka nit poziva na acceptEvent () metodom, slanjem događaja koji jesu eventTime od sada do "sada minus sto sekundi".
U međuvremenu se možemo pozvati na getEventsFromLastMinute () metoda koja će vratiti snimak događaja koji su unutar jednogminutnog prozora:
ConcurrentNavigableMap eventsFromLastMinute = eventWindowSort.getEventsFromLastMinute ();
Broj događaja u eventsFromLastMinute varirat će u svakom probnom radu, ovisno o brzini kojom će proizvođačke niti slati događaje na EventWindowSort. Možemo tvrditi da u vraćenom snimku nema niti jednog događaja koji je stariji od jedne minute:
long eventsOlderThanOneMinute = eventsFromLastMinute .entrySet () .stream () .filter (e -> e.getKey (). isBefore (ZonedDateTime.now (). minusMinutes (1))) .count (); assertEquals (eventsOlderThanOneMinute, 0);
I da na snimci ima više od nule događaja koji se nalaze u roku od jedne minute:
long eventYoungerThanOneMinute = eventsFromLastMinute .entrySet () .stream () .filter (e -> e.getKey (). isAfter (ZonedDateTime.now (). minusMinutes (1))) .count (); assertTrue (eventYoungerThanOneMinute> 0);
Naše getEventsFromLastMinute () koristi tailMap () ispod.
Isprobajmo sada getEventsOlderThatOneMinute () koji koristi headMap () metoda iz ConcurrentSkipListMap:
ConcurrentNavigableMap eventsFromLastMinute = eventWindowSort.getEventsOlderThatOneMinute ();
Ovaj put dobivamo snimku događaja starijih od jedne minute. Možemo ustvrditi da takvih događaja ima više od nule:
long eventsOlderThanOneMinute = eventsFromLastMinute .entrySet () .stream () .filter (e -> e.getKey (). isBefore (ZonedDateTime.now (). minusMinutes (1))) .count (); assertTrue (eventsOlderThanOneMinute> 0);
I sljedeće, da nema niti jednog događaja koji nije iz zadnje minute:
long eventYoungerThanOneMinute = eventsFromLastMinute .entrySet () .stream () .filter (e -> e.getKey (). isAfter (ZonedDateTime.now (). minusMinutes (1))) .count (); assertEquals (eventYoungerThanOneMinute, 0);
Najvažnije je napomenuti da je možemo napraviti snimku podataka dok druge niti još dodaju nove vrijednosti prema ConcurrentSkipListMap.
4. Zaključak
U ovom smo brzom vodiču pogledali osnove ConcurrentSkipListMap, zajedno s nekoliko praktičnih primjera.
Potaknuli smo visoke performanse ConcurrentSkipListMap za implementaciju neblokirajućeg algoritma koji nam može poslužiti nepromjenjivu snimku podataka čak i ako istodobno više niti ažurira kartu.
Implementacija svih ovih primjera i isječaka koda može se naći u projektu GitHub; ovo je Mavenov projekt, pa bi ga trebalo biti lako uvesti i pokrenuti kakav jest.