介绍java DelayQueue类
本文我们讨论java.util.concurrent 包中的DelayQueue类,是用于生产者和消费者模式的阻塞队列。它有一个非常有用的特性————当消费者想从队列中取消息时,只能获取已经过期一段时间的元素(延迟获取)。
在DelayQueue队列中实现元素延迟
每个需要放入DelayQueue队列元素需要实现Delayed接口,下面我们创建DelayObject 类,其实例对象将被放入DelayQueue中。其构造函数包括字符串类型数据及延迟毫秒变量:
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() 方法返回响应单位的时间量:
@Override
public long getDelay(TimeUnit unit) {
long diff = startTime - System.currentTimeMillis();
return unit.convert(diff, TimeUnit.MILLISECONDS);
}
当消费者尝试从队列中获取元素时,DelayQueue 队列执行getDelay()方法查找允许从队列中返回的对象。如果方法返回0或负数,则可以从队列中获取元素。
同时也需要实现compareTo() 方法,因为DelayQueue 队列中的元素根据有效期进行排序。先要过期的元素在队列的首位,而拥有最长有效期的元素在队尾:
@Override
public int compareTo(Delayed o) {
return Ints.saturatedCast(
this.startTime - ((DelayObject) o).startTime);
}
通过延迟队列实现生产者和消费者程序
为了测试DelayQueue 队列,我们需要实现生产者和消费者逻辑。生产者类需要队列、生产元素的数量以及每个元素延迟的毫秒作为参数。
然后执行run方法,在队列中加入元素后并休眠500毫秒:
public class DelayQueueProducer implements Runnable {
private BlockingQueue<DelayObject> 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();
}
}
}
}
消费者实现类似,但包括跟踪消费消息的数量:
public class DelayQueueConsumer implements Runnable {
private BlockingQueue<DelayObject> 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();
}
}
}
}
DelayQueue 应用测试
为了测试DelayQueue,我们创建一个生产者线程即一个消费者线程。生产者put两个对象至队列中,每个对象需要500毫秒延迟,测试断言消费者消费2个消息:
@Test
public void givenDelayQueue_whenProduceElement
_thenShouldConsumeAfterGivenDelay() throws InterruptedException {
// given
ExecutorService executor = Executors.newFixedThreadPool(2);
BlockingQueue<DelayObject> 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}
生产者在队列中加入第一个元素,等待过期后被消费。第二个元素也一样。
消费者在给定的时间内不能消费
生产者生产元素需要10秒后过期:
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 数量为0.
生产立即过期元素
如果实现延迟消息的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);
总结
本文我们学习了DelayQueue数据结构。通过实例实现生产延迟元素的生产者和消费者程序。利用DelayQueue队列的特性,消费者只能消费已经过期的元素。