Guia para DelayQueue
1. Visão geral
Neste artigo, veremos a construçãoDelayQueue do pacotejava.util.concurrent. Essa é uma fila de bloqueio que pode ser usada em programas produtor-consumidor.
Possui uma característica muito útil -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. ImplementandoDelayed para Elementos emDelayQueue
Cada elemento que queremos colocar emDelayQueue precisa implementar a interfaceDelayed. Digamos que queremos criar uma classeDelayObject. Instâncias dessa classe serão colocadas emDelayQueue.
Vamos passar os dadosString edelayInMilliseconds como e argumentos para seu construtor:
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;
}
Estamos definindo umstartTime – que é um momento em que o elemento deve ser consumido da fila. Em seguida, precisamos implementar o métodogetDelay() - ele deve retornar o atraso restante associado a este objeto na unidade de tempo fornecida.
Portanto, precisamos usar o métodoTimeUnit.convert() para retornar o atraso restante noTimeUnit: adequado
@Override
public long getDelay(TimeUnit unit) {
long diff = startTime - System.currentTimeMillis();
return unit.convert(diff, TimeUnit.MILLISECONDS);
}
Quando o consumidor tenta pegar um elemento da fila,DelayQueue executarágetDelay() para descobrir se aquele elemento pode ser retornado da fila. Se o métodogetDelay() retornar zero ou um número negativo, significa que ele pode ser recuperado da fila.
Também precisamos implementar o métodocompareTo(), porque os elementos emDelayQueue serão classificados de acordo com o tempo de expiração. O item que expirará primeiro é mantido no início da fila e o elemento com o maior tempo de expiração é mantido no final da fila:
@Override
public int compareTo(Delayed o) {
return Ints.saturatedCast(
this.startTime - ((DelayObject) o).startTime);
}
3. DelayQueue Consumer e produtor
Para poder testar nossoDelayQueue, precisamos implementar a lógica do produtor e do consumidor. A classe produtora usa como argumento a fila, o número de elementos a serem produzidos e o atraso de cada mensagem em milissegundos.
Então, quando o métodorun() é invocado, ele coloca os elementos na fila e dorme por 500 milissegundos após cada colocação:
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 é muito semelhante, mas também controla o número de mensagens que foram consumidas:
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 Teste de Uso
Para testar o comportamento deDelayQueue,, criaremos um encadeamento produtor e um encadeamento consumidor.
O produtor iráput() dois objetos na fila com 500 milissegundos de atraso. O teste afirma que o consumidor consumiu duas mensagens:
@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);
}
Podemos observar que a execução deste programa produzirá a seguinte saída:
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}
O produtor coloca o objeto e, após algum tempo, o primeiro objeto pelo qual o atraso expirou é consumido.
A mesma situação ocorreu para o segundo elemento.
5. O consumidor não é capaz de consumir no prazo determinado
Digamos que temos um produtor que está produzindo um elemento queexpire in 10 seconds:
int numberOfElementsToProduce = 1;
int delayOfEachProducedMessageMilliseconds = 10_000;
DelayQueueConsumer consumer = new DelayQueueConsumer(
queue, numberOfElementsToProduce);
DelayQueueProducer producer = new DelayQueueProducer(
queue, numberOfElementsToProduce, delayOfEachProducedMessageMilliseconds);
Começaremos nosso teste, mas ele será encerrado após 5 segundos. Devido às características doDelayQueue,, o consumidor não poderá consumir a mensagem da fila porque o elemento ainda não expirou:
executor.submit(producer);
executor.submit(consumer);
executor.awaitTermination(5, TimeUnit.SECONDS);
executor.shutdown();
assertEquals(consumer.numberOfConsumedElements.get(), 0);
Observe que onumberOfConsumedElements do consumidor tem um valor igual a zero.
6. Produzindo um Elemento com Expiração Imediata
Quando as implementações do métodoDelayed messagegetDelay() retornam um número negativo, significa que o elemento fornecido já expirou. Nessa situação, o produtor consumirá esse elemento imediatamente.
Podemos testar a situação de produção de um elemento com atraso negativo:
int numberOfElementsToProduce = 1;
int delayOfEachProducedMessageMilliseconds = -10_000;
DelayQueueConsumer consumer = new DelayQueueConsumer(queue, numberOfElementsToProduce);
DelayQueueProducer producer = new DelayQueueProducer(
queue, numberOfElementsToProduce, delayOfEachProducedMessageMilliseconds);
Quando iniciamos o caso de teste, o consumidor consumirá o elemento imediatamente porque ele já expirou:
executor.submit(producer);
executor.submit(consumer);
executor.awaitTermination(1, TimeUnit.SECONDS);
executor.shutdown();
assertEquals(consumer.numberOfConsumedElements.get(), 1);
7. Conclusão
Neste artigo, examinamos a construçãoDelayQueue do pacotejava.util.concurrent.
Implementamos um elementoDelayed que foi produzido e consumido da fila.
Aproveitamos nossa implementação deDelayQueue para consumir elementos que haviam expirado.
A implementação de todos esses exemplos e trechos de código pode ser encontrada emGitHub project - que é um projeto Maven, portanto, deve ser fácil de importar e executar como está.