介绍java DelayQueue类

介绍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队列的特性,消费者只能消费已经过期的元素。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值