Panduan untuk ConcurrentSkipListMap

1. Ikhtisar

Dalam artikel singkat ini, kita akan melihat kelas ConcurrentSkipListMap dari paket java.util.concurrent .

Konstruksi ini memungkinkan kita untuk membuat logika aman-thread dengan cara yang bebas kunci. Ini ideal untuk masalah ketika kita ingin membuat snapshot data yang tidak dapat diubah sementara utas lain masih memasukkan data ke dalam peta.

Kami akan memecahkan masalah pengurutan aliran peristiwa dan mendapatkan snapshot peristiwa yang datang dalam 60 detik terakhir menggunakan konstruksi itu .

2. Aliran Logika Penyortiran

Katakanlah kita memiliki aliran peristiwa yang terus-menerus datang dari banyak utas. Kami harus dapat mengambil peristiwa dari 60 detik terakhir, dan juga peristiwa yang lebih lama dari 60 detik.

Pertama, mari tentukan struktur data acara kita:

public class Event { private ZonedDateTime eventTime; private String content; // standard constructors/getters }

Kami ingin acara kami diurutkan menggunakan bidang eventTime . Untuk mencapai ini menggunakan ConcurrentSkipListMap, kita perlu meneruskan Comparator ke konstruktornya sambil membuat instance-nya:

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

Kami akan membandingkan semua acara yang tiba menggunakan stempel waktunya. Kami menggunakan metode CompareLong () dan meneruskan fungsi ekstrak yang dapat memakan waktu lama dari ZonedDateTime.

Saat acara kami tiba, kami hanya perlu menambahkannya ke peta menggunakan metode put () . Perhatikan bahwa metode ini tidak memerlukan sinkronisasi eksplisit apa pun:

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

The ConcurrentSkipListMap akan menangani menyortir dari peristiwa-peristiwa di bawah menggunakan Comparator yang dilewatkan ke dalam konstruktor.

Pro ConcurrentSkipListMap yang paling menonjol adalah metode yang dapat membuat snapshot datanya yang tidak dapat diubah dengan cara tanpa kunci. Untuk mendapatkan semua kejadian yang datang dalam satu menit terakhir, kita bisa menggunakan metode tailMap () dan melewatkan waktu dari mana kita ingin mendapatkan elemen:

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

Ini akan mengembalikan semua peristiwa dari menit terakhir. Ini akan menjadi snapshot yang tidak dapat diubah dan yang paling penting adalah thread penulisan lain dapat menambahkan acara baru ke ConcurrentSkipListMap tanpa perlu melakukan penguncian eksplisit.

Sekarang kita bisa mendapatkan semua peristiwa yang datang lebih lambat satu menit dari sekarang - dengan menggunakan metode headMap () :

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

Ini akan mengembalikan cuplikan tetap dari semua peristiwa yang lebih lama dari satu menit. Semua metode di atas milik kelas EventWindowSort , yang akan kita gunakan di bagian selanjutnya.

3. Menguji Logika Aliran Sortir

Setelah kita menerapkan logika pengurutan menggunakan ConcurrentSkipListMap, sekarang kita dapat mengujinya dengan membuat dua utas penulis yang masing-masing akan mengirim seratus kejadian:

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

Setiap utas memanggil metode acceptEvent () , mengirimkan kejadian yang memiliki eventTime dari sekarang ke "sekarang minus seratus detik".

Sementara itu, kita bisa memanggil metode getEventsFromLastMinute () yang akan mengembalikan snapshot peristiwa yang ada dalam jendela satu menit:

ConcurrentNavigableMap eventsFromLastMinute = eventWindowSort.getEventsFromLastMinute();

Jumlah peristiwa di eventsFromLastMinute akan bervariasi di setiap pengujian yang dijalankan tergantung pada kecepatan di mana thread produsen akan mengirim peristiwa ke EventWindowSort. Kita dapat menegaskan bahwa tidak ada satu pun peristiwa dalam snapshot yang dikembalikan yang lebih lama dari satu menit:

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

Dan ada lebih dari nol peristiwa dalam snapshot yang berada dalam jendela satu menit:

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

Kami getEventsFromLastMinute () menggunakan tailMap () di bawahnya.

Mari kita uji sekarang getEventsOlderThatOneMinute () yang menggunakan metode headMap () dari ConcurrentSkipListMap:

ConcurrentNavigableMap eventsFromLastMinute = eventWindowSort.getEventsOlderThatOneMinute();

Kali ini kami mendapatkan cuplikan peristiwa yang lebih lama dari satu menit. Kami dapat menegaskan bahwa ada lebih dari nol peristiwa seperti itu:

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

Dan selanjutnya, tidak ada satu peristiwa pun yang berasal dari dalam menit terakhir:

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

Hal terpenting yang perlu diperhatikan adalah kita dapat mengambil snapshot data sementara thread lain masih menambahkan nilai baru ke ConcurrentSkipListMap.

4. Kesimpulan

Dalam tutorial singkat ini, kita telah melihat dasar-dasar ConcurrentSkipListMap , bersama dengan beberapa contoh praktis .

Kami memanfaatkan kinerja tinggi ConcurrentSkipListMap untuk mengimplementasikan algoritme non-pemblokiran yang dapat memberi kami snapshot data yang tidak dapat diubah meskipun pada saat yang sama beberapa utas memperbarui peta.

Penerapan semua contoh dan cuplikan kode ini dapat ditemukan di proyek GitHub; ini adalah proyek Maven, jadi semestinya mudah untuk mengimpor dan menjalankannya apa adanya.