ConcurrentSkipListMapのガイド

ConcurrentSkipListMapのガイド

1. 概要

この簡単な記事では、java.util.concurrentパッケージのConcurrentSkipListMapクラスについて説明します。

この構成により、ロックフリーな方法でスレッドセーフなロジックを作成できます。 他のスレッドがまだマップにデータを挿入している間に、データの不変のスナップショットを作成したい場合の問題に最適です。

sorting a stream of events and getting a snapshot of the events that arrived in the last 60 seconds using that constructの問題を解決します。

2. ストリームソートロジック

複数のスレッドから継続的に発生する一連のイベントがあるとします。 過去60秒のイベント、および60秒より古いイベントを取得できる必要があります。

まず、イベントデータの構造を定義しましょう。

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

    // standard constructors/getters
}

eventTimeフィールドを使用してイベントをソートしたままにしておきます。 ConcurrentSkipListMap,を使用してこれを実現するには、インスタンスの作成中にComparatorをコンストラクターに渡す必要があります。

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

到着したすべてのイベントをタイムスタンプを使用して比較します。 comparingLong()メソッドを使用して、ZonedDateTime.からlongタイムスタンプを取得できるextract関数を渡します。

イベントが到着したら、put()メソッドを使用してイベントをマップに追加するだけで済みます。 このメソッドは、明示的な同期を必要としないことに注意してください。

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

ConcurrentSkipListMapは、コンストラクターで渡されたComparatorを使用して、その下にあるイベントの並べ替えを処理します。

ConcurrentSkipListMapの最も注目すべき長所は、ロックフリーの方法でデータの不変のスナップショットを作成できる方法です。 過去1分以内に到着したすべてのイベントを取得するには、tailMap()メソッドを使用して、要素を取得する時間を渡すことができます。

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

過去1分間のすべてのイベントが返されます。 これは不変のスナップショットになります。最も重要なのは、他の書き込みスレッドが明示的なロックを行うことなく、ConcurrentSkipListMapに新しいイベントを追加できることです。

これで、headMap()メソッドを使用して、今から1分後に到着したすべてのイベントを取得できます。

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

これにより、1分以上経過したすべてのイベントの不変のスナップショットが返されます。 上記のメソッドはすべてEventWindowSortクラスに属しており、次のセクションで使用します。

3. ソートストリームロジックのテスト

ConcurrentSkipListMap,を使用して並べ替えロジックを実装すると、それぞれ100個のイベントを送信するtest it by creating two writer threadsを実行できるようになります。

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

各スレッドはacceptEvent()メソッドを呼び出し、eventTimeを持つイベントを今から「今マイナス100秒」に送信します。

それまでの間、1分間のウィンドウ内にあるイベントのスナップショットを返すgetEventsFromLastMinute()メソッドを呼び出すことができます。

ConcurrentNavigableMap eventsFromLastMinute
  = eventWindowSort.getEventsFromLastMinute();

eventsFromLastMinuteのイベント数は、プロデューサースレッドがEventWindowSort.にイベントを送信する速度に応じて、各テスト実行で変化します。に単一のイベントはないと断言できます。 1分より古い返されるスナップショット:

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

assertEquals(eventsOlderThanOneMinute, 0);

そして、スナップショットには1分以内にゼロを超えるイベントがあります:

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

assertTrue(eventYoungerThanOneMinute > 0);

getEventsFromLastMinute()は、その下にあるtailMap()を使用します。

ConcurrentSkipListMap:からheadMap()メソッドを使用しているgetEventsOlderThatOneMinute()をテストしてみましょう

ConcurrentNavigableMap eventsFromLastMinute
  = eventWindowSort.getEventsOlderThatOneMinute();

今回は、1分以上前のイベントのスナップショットを取得します。 そのようなイベントはゼロ以上あると断言できます。

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

assertTrue(eventsOlderThanOneMinute > 0);

そして次に、土壇場内からのイベントは1つもありません。

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

assertEquals(eventYoungerThanOneMinute, 0);

注意すべき最も重要なことは、we can take the snapshot of data while other threads are still adding new valuesからConcurrentSkipListMap.へのことです。

4. 結論

このクイックチュートリアルでは、ConcurrentSkipListMapの基本と、いくつかの実用的な例.を確認しました。

ConcurrentSkipListMapの高性能を活用して、複数のスレッドが同時にマップを更新している場合でも、データの不変のスナップショットを提供できる非ブロッキングアルゴリズムを実装しました。

これらすべての例とコードスニペットの実装は、GitHub projectにあります。これはMavenプロジェクトであるため、そのままインポートして実行するのは簡単です。