Leitfaden zu DelayQueue

Anleitung zu DelayQueue

1. Überblick

In diesem Artikel betrachten wir das KonstruktDelayQueueaus dem Paketjava.util.concurrent. Dies ist eine Sperrwarteschlange, die in Producer-Consumer-Programmen verwendet werden kann.

Es hat eine sehr nützliche Eigenschaft -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. Implementieren vonDelayed für Elemente inDelayQueue

Jedes Element, das inDelayQueue eingefügt werden soll, muss die SchnittstelleDelayed implementieren. Nehmen wir an, wir möchten eineDelayObject-Klasse erstellen. Instanzen dieser Klasse werden inDelayQueue. eingegeben

Wir übergeben die Daten vonStringunddelayInMillisecondsals und Argumente an den Konstruktor:

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

Wir definieren einstartTime –. Dies ist eine Zeit, in der das Element aus der Warteschlange verbraucht werden soll. Als nächstes müssen wir diegetDelay()-Methode implementieren - sie sollte die verbleibende Verzögerung zurückgeben, die diesem Objekt in der angegebenen Zeiteinheit zugeordnet ist.

Daher müssen wir die MethodeTimeUnit.convert() verwenden, um die verbleibende Verzögerung in den richtigenTimeUnit: zurückzugeben

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

Wenn der Verbraucher versucht, ein Element aus der Warteschlange zu entnehmen, führtDelayQueuegetDelay() aus, um herauszufinden, ob dieses Element aus der Warteschlange zurückgegeben werden darf. Wenn diegetDelay()-Methode Null oder eine negative Zahl zurückgibt, bedeutet dies, dass sie aus der Warteschlange abgerufen werden kann.

Wir müssen auch die MethodecompareTo() implementieren, da die Elemente inDelayQueue nach der Ablaufzeit sortiert werden. Das Element, das zuerst abläuft, wird am Anfang der Warteschlange und das Element mit der höchsten Ablaufzeit am Ende der Warteschlange aufbewahrt:

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

3. DelayQueue Cohn und Produzent

Um unsereDelayQueue testen zu können, müssen wir Produzenten- und Konsumentenlogik implementieren. Die Producer-Klasse verwendet die Warteschlange, die Anzahl der zu produzierenden Elemente und die Verzögerung jeder Nachricht in Millisekunden als Argumente.

Wenn dann dierun()-Methode aufgerufen wird, werden Elemente in die Warteschlange gestellt und nach jedem Put 500 Millisekunden lang in den Ruhezustand versetzt:

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 ist sehr ähnlich, verfolgt aber auch die Anzahl der Nachrichten, die verbraucht wurden:

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 Verwendungstest

Um das Verhalten derDelayQueue,zu testen, erstellen wir einen Producer-Thread und einen Consumer-Thread.

Der Produzent wirdput() zwei Objekte mit einer Verzögerung von 500 Millisekunden in die Warteschlange stellen. Der Test bestätigt, dass der Verbraucher zwei Nachrichten konsumiert hat:

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

Wir können beobachten, dass das Ausführen dieses Programms die folgende Ausgabe erzeugt:

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}

Der Produzent stellt das Objekt und nach einer Weile wird das erste Objekt verbraucht, für das die Verzögerung abgelaufen ist.

Die gleiche Situation trat für das zweite Element auf.

5. Verbraucher nicht in der Lage, in der vorgegebenen Zeit zu konsumieren

Nehmen wir an, wir haben einen Produzenten, der ein Element produziert, dasexpire in 10 seconds wird:

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

Wir werden unseren Test starten, er wird jedoch nach 5 Sekunden beendet. Aufgrund der Eigenschaften vonDelayQueue, kann der Verbraucher die Nachricht nicht aus der Warteschlange verwenden, da das Element noch nicht abgelaufen ist:

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

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

Beachten Sie, dassnumberOfConsumedElements des Verbrauchers einen Wert gleich Null hat.

6. Erstellen eines Elements mit sofortigem Ablauf

Wenn die Implementierungen der MethodeDelayed messagegetDelay() eine negative Zahl zurückgeben, bedeutet dies, dass das angegebene Element bereits abgelaufen ist. In diesem Fall wird der Hersteller dieses Element sofort verbrauchen.

Wir können die Situation der Erzeugung eines Elements mit negativer Verzögerung testen:

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

Wenn wir den Testfall starten, verbraucht der Consumer das Element sofort, da es bereits abgelaufen ist:

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

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

7. Fazit

In diesem Artikel haben wir uns das KonstruktDelayQueueaus dem Paketjava.util.concurrentangesehen.

Wir haben einDelayed-Element implementiert, das aus der Warteschlange erstellt und verbraucht wurde.

Wir haben unsere Implementierung derDelayQueue genutzt, um abgelaufene Elemente zu verbrauchen.

Die Implementierung all dieser Beispiele und Codefragmente finden Sie inGitHub project - einem Maven-Projekt. Daher sollte es einfach zu importieren und auszuführen sein.