DelayQueueのガイド

DelayQueueのガイド

1. 概要

この記事では、java.util.concurrentパッケージのDelayQueueコンストラクトについて説明します。 これは、プロデューサー/コンシューマープログラムで使用できるブロッキングキューです。

非常に便利な特性があります–when the consumer wants to take an element from the queue, they can take it only when the delay for that particular element has expired.

2. DelayQueueの要素にDelayedを実装する

DelayQueueに配置する各要素は、Delayedインターフェイスを実装する必要があります。 DelayObjectクラスを作成するとします。 そのクラスのインスタンスはDelayQueue.に配置されます

StringデータとdelayInMillisecondsをコンストラクターに引数として渡します。

public class DelayObject implements Delayed {
    private String data;
    private long startTime;

    public DelayObject(String data, long delayInMilliseconds) {
        this.data = data;
        this.startTime = System.currentTimeMillis() + delayInMilliseconds;
    }

startTime –を定義しています。これは、要素がキューから消費される必要がある時間です。 次に、getDelay()メソッドを実装する必要があります。このメソッドは、指定された時間単位でこのオブジェクトに関連付けられた残りの遅延を返す必要があります。

したがって、TimeUnit.convert()メソッドを使用して、適切なTimeUnit:の残りの遅延を返す必要があります。

@Override
public long getDelay(TimeUnit unit) {
    long diff = startTime - System.currentTimeMillis();
    return unit.convert(diff, TimeUnit.MILLISECONDS);
}

コンシューマーがキューから要素を取得しようとすると、DelayQueuegetDelay()を実行して、その要素がキューから返されることを許可されているかどうかを確認します。 getDelay()メソッドがゼロまたは負の数を返す場合は、キューから取得できることを意味します。

また、DelayQueueの要素は有効期限に従ってソートされるため、compareTo()メソッドを実装する必要があります。 最初に期限切れになるアイテムはキューの先頭に保持され、有効期限が最も長い要素はキューの末尾に保持されます。

@Override
public int compareTo(Delayed o) {
    return Ints.saturatedCast(
      this.startTime - ((DelayObject) o).startTime);
}

3. DelayQueue Consumerおよびプロデューサー

DelayQueueをテストできるようにするには、プロデューサーロジックとコンシューマーロジックを実装する必要があります。 プロデューサークラスは、キュー、生成する要素の数、および各メッセージの遅延(ミリ秒単位)を引数として受け取ります。

次に、run()メソッドが呼び出されると、要素がキューに入れられ、入れられるたびに500ミリ秒スリープします。

public class DelayQueueProducer implements Runnable {

    private BlockingQueue queue;
    private Integer numberOfElementsToProduce;
    private Integer delayOfEachProducedMessageMilliseconds;

    // standard constructor

    @Override
    public void run() {
        for (int i = 0; i < numberOfElementsToProduce; i++) {
            DelayObject object
              = new DelayObject(
                UUID.randomUUID().toString(), delayOfEachProducedMessageMilliseconds);
            System.out.println("Put object: " + object);
            try {
                queue.put(object);
                Thread.sleep(500);
            } catch (InterruptedException ie) {
                ie.printStackTrace();
            }
        }
    }
}

The consumer implementationは非常に似ていますが、消費されたメッセージの数も追跡します。

public class DelayQueueConsumer implements Runnable {
    private BlockingQueue queue;
    private Integer numberOfElementsToTake;
    public AtomicInteger numberOfConsumedElements = new AtomicInteger();

    // standard constructors

    @Override
    public void run() {
        for (int i = 0; i < numberOfElementsToTake; i++) {
            try {
                DelayObject object = queue.take();
                numberOfConsumedElements.incrementAndGet();
                System.out.println("Consumer take: " + object);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

4. DelayQueue使用テスト

DelayQueue,の動作をテストするために、1つのプロデューサースレッドと1つのコンシューマースレッドを作成します。

プロデューサーは、500ミリ秒の遅延で2つのオブジェクトをキューにput()します。 このテストでは、コンシューマーが2つのメッセージを消費したと断言します。

@Test
public void givenDelayQueue_whenProduceElement
  _thenShouldConsumeAfterGivenDelay() throws InterruptedException {
    // given
    ExecutorService executor = Executors.newFixedThreadPool(2);

    BlockingQueue queue = new DelayQueue<>();
    int numberOfElementsToProduce = 2;
    int delayOfEachProducedMessageMilliseconds = 500;
    DelayQueueConsumer consumer = new DelayQueueConsumer(
      queue, numberOfElementsToProduce);
    DelayQueueProducer producer = new DelayQueueProducer(
      queue, numberOfElementsToProduce, delayOfEachProducedMessageMilliseconds);

    // when
    executor.submit(producer);
    executor.submit(consumer);

    // then
    executor.awaitTermination(5, TimeUnit.SECONDS);
    executor.shutdown();

    assertEquals(consumer.numberOfConsumedElements.get(), numberOfElementsToProduce);
}

このプログラムを実行すると、次の出力が生成されることがわかります。

Put object: {data='86046157-e8a0-49b2-9cbb-8326124bcab8', startTime=1494069868007}
Consumer take: {data='86046157-e8a0-49b2-9cbb-8326124bcab8', startTime=1494069868007}
Put object: {data='d47927ef-18c7-449b-b491-5ff30e6795ed', startTime=1494069868512}
Consumer take: {data='d47927ef-18c7-449b-b491-5ff30e6795ed', startTime=1494069868512}

プロデューサーはオブジェクトを配置し、しばらくすると、遅延が期限切れになった最初のオブジェクトが消費されます。

同じ状況が2番目の要素でも発生しました。

5. 消費者は与えられた時間内に消費することができません

expire in 10 secondsになる要素を生成しているプロデューサーがあるとしましょう。

int numberOfElementsToProduce = 1;
int delayOfEachProducedMessageMilliseconds = 10_000;
DelayQueueConsumer consumer = new DelayQueueConsumer(
  queue, numberOfElementsToProduce);
DelayQueueProducer producer = new DelayQueueProducer(
  queue, numberOfElementsToProduce, delayOfEachProducedMessageMilliseconds);

テストを開始しますが、5秒後に終了します。 DelayQueue,の特性により、要素がまだ期限切れになっていないため、コンシューマーはキューからのメッセージを消費できません。

executor.submit(producer);
executor.submit(consumer);

executor.awaitTermination(5, TimeUnit.SECONDS);
executor.shutdown();
assertEquals(consumer.numberOfConsumedElements.get(), 0);

コンシューマーのnumberOfConsumedElementsの値はゼロに等しいことに注意してください。

6. 有効期限がすぐに切れる要素の作成

DelayedメッセージgetDelay()メソッドの実装が負の数を返す場合、それは指定された要素がすでに期限切れになっていることを意味します。 この状況では、プロデューサーはその要素をすぐに消費します。

負の遅延を持つ要素を生成する状況をテストできます。

int numberOfElementsToProduce = 1;
int delayOfEachProducedMessageMilliseconds = -10_000;
DelayQueueConsumer consumer = new DelayQueueConsumer(queue, numberOfElementsToProduce);
DelayQueueProducer producer = new DelayQueueProducer(
  queue, numberOfElementsToProduce, delayOfEachProducedMessageMilliseconds);

テストケースを開始すると、消費者は既に有効期限が切れているため、すぐに要素を消費します。

executor.submit(producer);
executor.submit(consumer);

executor.awaitTermination(1, TimeUnit.SECONDS);
executor.shutdown();
assertEquals(consumer.numberOfConsumedElements.get(), 1);

7. 結論

この記事では、java.util.concurrentパッケージからのDelayQueueコンストラクトを見ていました。

キューから生成および消費されるDelayed要素を実装しました。

DelayQueueの実装を活用して、期限切れの要素を消費しました。

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