Panduan untuk ConcurrentSkipListMap

1. Gambaran keseluruhan

Dalam artikel ringkas ini, kita akan melihat kelas ConcurrentSkipListMap dari pakej java.util.concurrent .

Konstruk ini membolehkan kita membuat logik selamat utas dengan cara bebas kunci. Ia sangat sesuai untuk masalah ketika kita ingin membuat gambar yang tidak berubah sementara utas lain masih memasukkan data ke dalam peta.

Kami akan menyelesaikan masalah menyusun aliran peristiwa dan mendapatkan gambaran ringkas peristiwa yang tiba dalam 60 saat terakhir menggunakan konstruk tersebut .

2. Logik Menyusun Aliran

Katakan bahawa kita mempunyai aliran peristiwa yang terus-menerus datang dari pelbagai utas. Kita perlu dapat mengambil acara dari 60 saat terakhir, dan juga acara yang lebih tua dari 60 saat.

Pertama, mari tentukan struktur data acara kami:

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

Kami ingin memastikan acara kami disusun menggunakan medan eventTime . Untuk mencapainya dengan menggunakan ConcurrentSkipListMap, kita perlu meneruskan Comparator ke konstruktornya sambil membuat contohnya:

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

Kami akan membandingkan semua acara yang tiba menggunakan cap waktu mereka. Kami menggunakan kaedah membandingkan ( Long ) (dan membandingkan fungsi ekstrak yang boleh mengambil cap waktu yang lama dari ZonedDateTime.

Apabila acara kami tiba, kami hanya perlu menambahkannya ke peta menggunakan kaedah put () . Perhatikan bahawa kaedah ini tidak memerlukan penyegerakan eksplisit:

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

The ConcurrentSkipListMap akan menangani penyusunan peristiwa-peristiwa di bawahnya menggunakan Comparator yang diserahkan kepadanya dalam konstruktor.

Kelebihan yang paling terkenal dari ConcurrentSkipListMap adalah kaedah yang dapat membuat snapshot data yang tidak berubah dengan cara bebas kunci. Untuk mendapatkan semua acara yang tiba dalam satu minit terakhir, kita dapat menggunakan kaedah tailMap () dan melewati masa dari mana kita ingin mendapatkan elemen:

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

Ia akan mengembalikan semua acara dari minit terakhir. Ini akan menjadi gambaran yang tidak dapat diubah dan apa yang paling penting adalah bahawa utas penulisan lain dapat menambahkan acara baru ke ConcurrentSkipListMap tanpa perlu melakukan penguncian eksplisit.

Kita sekarang boleh mendapatkan semua acara yang tiba kemudian satu minit dari sekarang - dengan menggunakan kaedah headMap () :

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

Ini akan menghasilkan gambaran yang tidak dapat diubah dari semua peristiwa yang lebih tua dari satu minit. Semua kaedah di atas tergolong dalam kelas EventWindowSort , yang akan kami gunakan di bahagian seterusnya.

3. Menguji Logik Sorting Stream

Sebaik sahaja kami menerapkan logik penyortiran kami menggunakan ConcurrentSkipListMap, kami kini dapat mengujinya dengan membuat dua utas penulis yang akan menghantar seratus acara masing-masing:

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 menggunakan kaedah acceptEvent () , mengirimkan acara yang memiliki eventTime dari sekarang hingga "sekarang minus seratus saat".

Sementara itu, kita boleh menggunakan kaedah getEventsFromLastMinute () yang akan mengembalikan snapshot peristiwa yang berada dalam tetingkap satu minit:

ConcurrentNavigableMap eventsFromLastMinute = eventWindowSort.getEventsFromLastMinute();

Bilangan acara dalam eventsFromLastMinute akan berbeza-beza dalam setiap ujian dijalankan bergantung kepada kelajuan di mana benang pengeluar akan menghantar peristiwa kepada EventWindowSort. Kami dapat menegaskan bahawa tidak ada satu peristiwa pun dalam snapshot yang dikembalikan yang lebih tua dari satu minit:

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

Dan terdapat lebih daripada sifar peristiwa dalam gambar yang berada dalam tetingkap satu minit:

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

GetEventsFromLastMinute kami () menggunakan tailMap () di bawahnya.

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

ConcurrentNavigableMap eventsFromLastMinute = eventWindowSort.getEventsOlderThatOneMinute();

Kali ini kami mendapat gambaran ringkas peristiwa yang lebih tua dari satu minit. Kita boleh menegaskan bahawa terdapat lebih daripada sifar peristiwa seperti itu:

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

Dan seterusnya, bahawa tidak ada satu peristiwa pun dari saat-saat terakhir:

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

Perkara yang paling penting untuk diperhatikan ialah kita dapat mengambil gambar data sementara utas lain masih menambah nilai baru ke ConcurrentSkipListMap.

4. Kesimpulan

Dalam tutorial ringkas ini, kami telah melihat asas-asas ConcurrentSkipListMap , bersama dengan beberapa contoh praktikal .

Kami memanfaatkan prestasi tinggi ConcurrentSkipListMap untuk menerapkan algoritma tanpa penyekat yang dapat memberikan gambaran data yang tidak dapat diubah kepada kami walaupun pada masa yang sama banyak utas mengemas kini peta.

Pelaksanaan semua contoh dan coretan kod ini terdapat dalam projek GitHub; ini adalah projek Maven, jadi mudah untuk diimport dan dijalankan sebagaimana adanya.