DelayQueue - Feature ImageDelayQueue - Feature Image
HappyCoders Glasses

DelayQueue in Java
(mit Beispiel)

Sven Woltmann
Sven Woltmann
Aktualisiert: 27. November 2024

In diesem und den nächsten Teilen dieser Tutorialserie geht es um Queues für spezielle Einsatzzwecke. Wir beginnen mit der DelayQueue, einer Queue, die die Elemente nach Ablaufzeit sortiert herausgibt.

Hier befinden wir uns in der Klassenhierarchie:

DelayQueue in der Klassenhierarchie
DelayQueue in der Klassenhierarchie

DelayQueue Eigenschaften

Die Klasse java.util.concurrent.DelayQueue ist – genau wie die PriorityQueue, die sie intern verwendet – keine FIFO-Queue. Es wird nicht das Element entnommen, das sich am längsten in der Queue befindet. Stattdessen wird ein Element dann entnommen, wenn eine diesem Element zugeordnete Wartezeit ("delay") abgelaufen ist.

Dazu müssen die Elemente das Interface java.util.concurrent.Delayed und dessen getDelay​()-Methode implementieren. Diese Methode liefert bei jeden Aufruf die restliche Wartezeit zurück, die noch ablaufen muss, bis das Element aus der Queue entnommen werden darf.

DelayQueue ist blockierend, aber unbounded, kann also beliebig viele Elemente aufnehmen und blockiert nur bei der Entnahme (bis die Wartezeit abgelaufen ist), nicht beim Einfügen.

Threadsicherheit wird durch pessimistisches Locking über ein einzelnes ReentrantLock sichergestellt.

Die Eigenschaften im Detail:

Unterliegende DatenstrukturThread-safe?Blocking/
Non-blocking
Fairness
Policy
Bounded/
Unbounded
Iterator Type
Priority QueueJa
(pessimistisches Locking mit einem Lock)
Blocking
(nur dequeue)
Nicht verfügbarUnboundedWeakly consistent¹

¹ Weakly consistent: Alle Elemente, die zum Zeitpunkt der Erzeugung des Interators in der Queue liegen, werden vom Iterator genau einmal durchlaufen. Änderungen, die danach erfolgen, können – müssen aber nicht – durch den Iterator berücksichtigt werden.

Einsatzempfehlung

Ich habe die DelayQueue noch nie benötigt und kann sie für keinen mir bekannten, sinnvollen Einsatzzweck empfehlen. Sie wird im JDK nur ein einziges Mal genutzt (in einer alten Swing-Klasse, die mit einem ScheduledExecutorService wahrscheinlich eleganter hätte implementiert werden können). Von daher ist nicht ausgeschlossen, dass sie unentdeckte Fehler enthält.

DelayQueue Beispiel

In folgendem Beispiel (→ Code auf GitHub) wird eine DelayQueue mit Instanzen der Klasse DelayedElement gefüllt, welche eine Zufallszahl enthalten sowie ein zufälliges initiales Delay zwischen 100 und 1.000 ms. Danach wird so oft poll() aufgerufen, bis die Queue wieder leer ist.

public class DelayQueueExample {
  public static void main(String[] args) {
    BlockingQueue<DelayedElement<Integer>> queue = new DelayQueue<>();
    ThreadLocalRandom random = ThreadLocalRandom.current();
    long startTime = System.currentTimeMillis();

    // Enqueue random numbers with random initial delays
    for (int i = 0; i < 7; i++) {
      int randomNumber = random.nextInt(10, 100);
      int initialDelayMillis = random.nextInt(100, 1000);
      DelayedElement<Integer> element = 
          new DelayedElement<>(randomNumber, initialDelayMillis);
      queue.offer(element);
      System.out.printf(
          "[%3dms] queue.offer(%s)   --> queue = %s%n",
          System.currentTimeMillis() - startTime, element, queue);
    }

    // Dequeue all elements
    while (!queue.isEmpty()) {
      try {
        DelayedElement<Integer> element = queue.take();
        System.out.printf(
            "[%3dms] queue.poll() = %s --> queue = %s%n",
            System.currentTimeMillis() - startTime, element, queue);
      } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
      }
    }
  }
}Code-Sprache: Java (java)

Und hier die zugehörige Klasse DelayedElement (→ Code auf GitHub). Die Sortierung ist – um den Code nicht noch länger zu machen – nicht stabil. D. h. wenn zwei Elemente mit derselben Wartezeit in die Queue gestellt werden, werden sie in zufälliger Reihenfolge wieder entnommen.

public class DelayedElement<E extends Comparable<E>> implements Delayed {
  private final E e;
  private final long initialDelayMillis;
  private final long expiration;

  public DelayedElement(E e, long initialDelayMillis) {
    this.e = e;
    this.initialDelayMillis = initialDelayMillis;
    this.expiration = System.currentTimeMillis() + initialDelayMillis;
  }

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

  @Override
  public int compareTo(Delayed o) {
    DelayedElement<?> other = (DelayedElement<?>) o;
    return Long.compare(expiration, other.expiration);
  }

  @Override
  public String toString() {
    return "{%s, %dms}".formatted(e, initialDelayMillis);
  }
}
Code-Sprache: Java (java)

Hier eine beispielhafte Ausgabe des Programms. Es ist gut zu sehen, wie die Queue zwar nicht sortiert ist¹, das Element mit der kürzesten Wartezeit sich jedoch immer vorne (links) befindet und dass die Elemente (ungefähr) nach Ablauf ihrer jeweiligen Wartezeit entnommen werden:

[  1ms] queue.offer({19, 713ms})   --> queue = [{19, 713ms}]
[ 28ms] queue.offer({15, 643ms})   --> queue = [{15, 643ms}, {19, 713ms}]
[ 29ms] queue.offer({35, 253ms})   --> queue = [{35, 253ms}, {19, 713ms}, {15, 643ms}]
[ 29ms] queue.offer({11, 781ms})   --> queue = [{35, 253ms}, {19, 713ms}, {15, 643ms}, {11, 781ms}]
[ 29ms] queue.offer({17, 844ms})   --> queue = [{35, 253ms}, {19, 713ms}, {15, 643ms}, {11, 781ms}, {17, 844ms}]
[ 29ms] queue.offer({40, 490ms})   --> queue = [{35, 253ms}, {19, 713ms}, {40, 490ms}, {11, 781ms}, {17, 844ms}, {15, 643ms}]
[ 30ms] queue.offer({39, 119ms})   --> queue = [{39, 119ms}, {19, 713ms}, {35, 253ms}, {11, 781ms}, {17, 844ms}, {15, 643ms}, {40, 490ms}]
[151ms] queue.poll() = {39, 119ms} --> queue = [{35, 253ms}, {19, 713ms}, {40, 490ms}, {11, 781ms}, {17, 844ms}, {15, 643ms}]
[283ms] queue.poll() = {35, 253ms} --> queue = [{40, 490ms}, {19, 713ms}, {15, 643ms}, {11, 781ms}, {17, 844ms}]
[520ms] queue.poll() = {40, 490ms} --> queue = [{15, 643ms}, {19, 713ms}, {17, 844ms}, {11, 781ms}]
[673ms] queue.poll() = {15, 643ms} --> queue = [{19, 713ms}, {11, 781ms}, {17, 844ms}]
[716ms] queue.poll() = {19, 713ms} --> queue = [{11, 781ms}, {17, 844ms}]
[811ms] queue.poll() = {11, 781ms} --> queue = [{17, 844ms}]
[874ms] queue.poll() = {17, 844ms} --> queue = []Code-Sprache: Klartext (plaintext)

¹ Tatsächlich sieht man die Reihenfolge der Elemente in der Array-Repräsentation des Min-Heaps.

Zusammenfassung und Ausblick

In diesem Artikel hast du alles über die DelayQueue, ihre Eigenschaften und ihre Verwendung erfahren.

Im nächsten Teil dieser Serie stelle ich dir weitere Spezialqueue vor – eine die niemals Elemente enthält: die SynchronousQueue.

Wenn du noch Fragen hast, stelle sie gerne über die Kommentar-Funktion. Möchtest du über neue Tutorials und Artikel informiert werden? Dann klicke hier, um dich für den HappyCoders.eu-Newsletter anzumelden.