Guide de DelayQueue

Guide de DelayQueue

1. Vue d'ensemble

Dans cet article, nous allons examiner la constructionDelayQueue du packagejava.util.concurrent. Il s'agit d'une file d'attente bloquante qui pourrait être utilisée dans les programmes producteur-consommateur.

Il a une caractéristique très utile -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. Implémentation deDelayed pour les éléments dans lesDelayQueue

Chaque élément que nous voulons mettre dans leDelayQueue doit implémenter l'interfaceDelayed. Disons que nous voulons créer une classeDelayObject. Les instances de cette classe seront placées dans lesDelayQueue.

Nous allons passer les donnéesString etdelayInMilliseconds en tant que arguments et à son constructeur:

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

Nous définissons unstartTime –, c'est le moment où l'élément doit être consommé depuis la file d'attente. Ensuite, nous devons implémenter la méthodegetDelay() - elle devrait renvoyer le délai restant associé à cet objet dans l'unité de temps donnée.

Par conséquent, nous devons utiliser la méthodeTimeUnit.convert() pour renvoyer le délai restant dans lesTimeUnit: appropriés

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

Lorsque le consommateur essaie de prendre un élément de la file d'attente, lesDelayQueue exécutentgetDelay() pour savoir si cet élément est autorisé à être renvoyé de la file d'attente. Si la méthodegetDelay() renvoie zéro ou un nombre négatif, cela signifie qu'elle pourrait être récupérée de la file d'attente.

Nous devons également implémenter la méthodecompareTo(), car les éléments desDelayQueue seront triés en fonction de l'heure d'expiration. L'élément qui expirera en premier est conservé en tête de la file d'attente et l'élément avec le délai d'expiration le plus élevé est conservé en fin de file d'attente:

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

3. DelayQueue Cconsommateurs et producteur

Pour pouvoir tester nosDelayQueue, nous devons implémenter la logique du producteur et du consommateur. La classe de producteur prend la file d'attente, le nombre d'éléments à produire et le délai de chaque message en millisecondes comme arguments.

Ensuite, lorsque la méthoderun() est appelée, elle met des éléments dans la file d'attente et se met en veille pendant 500 millisecondes après chaque put:

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 est très similaire, mais il garde également une trace du nombre de messages qui ont été consommés:

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. Test d'utilisation deDelayQueue

Pour tester le comportement desDelayQueue,, nous allons créer un thread producteur et un thread consommateur.

Le producteur placeraput()deux objets dans la file d'attente avec un délai de 500 millisecondes. Le test affirme que le consommateur a consommé deux messages:

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

Nous pouvons observer que l'exécution de ce programme produira le résultat suivant:

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}

Le producteur place l'objet et, après un certain temps, le premier objet pour lequel le délai a expiré est consommé.

La même situation s'est produite pour le deuxième élément.

5. Consommateur incapable de consommer dans le temps imparti

Disons que nous avons un producteur qui produit un élément qui vaexpire in 10 seconds:

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

Nous allons commencer notre test, mais il se terminera au bout de 5 secondes. En raison des caractéristiques desDelayQueue,, le consommateur ne pourra pas consommer le message de la file d'attente car l'élément n'est pas encore expiré:

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

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

Notez que lenumberOfConsumedElements du consommateur a une valeur égale à zéro.

6. Produire un élément avec une expiration immédiate

Lorsque les implémentations de la méthodeDelayed messagegetDelay() renvoient un nombre négatif, cela signifie que l'élément donné a déjà expiré. Dans cette situation, le producteur consommera cet élément immédiatement.

Nous pouvons tester la situation de production d’un élément avec un retard négatif:

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

Lorsque nous commençons le scénario de test, le consommateur consommera l'élément immédiatement car il a déjà expiré:

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

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

7. Conclusion

Dans cet article, nous examinions la constructionDelayQueue du packagejava.util.concurrent.

Nous avons implémenté un élémentDelayed qui a été produit et consommé à partir de la file d'attente.

Nous avons tiré parti de notre implémentation desDelayQueue pour consommer des éléments qui avaient expiré.

L'implémentation de tous ces exemples et extraits de code se trouve dans leGitHub project - qui est un projet Maven, il devrait donc être facile à importer et à exécuter tel quel.