延迟队列DelayQueue浅析

DelayQueue简介

1.DelayQueue是一个无界阻塞队列,队列内部使用PriorityQueue来实现。要添加进去的元素必须实现Delayed接口的类对象,在创建元素时可以指定多久才能从队列中获取当前元素,只有在延迟期满时才能从中提取元素;

2.该队列头部是延迟期满后保存时间最长的Delayed元素;

3.如果延迟都没有期满,则队列没有头部,并且poll将返回null;

4.当一个元素的getDelay(TimeUnit.NANOSECONDS)方法返回一个小于等于0的值时,表示该元素到期了;

5.无法使用poll或take移除未到期的元素,也不会将这些元素作为正常元素对待;例如:size方法同事返回到期和未到期元素的计数。

6.此队列不允许使用null元素。

怎么用

首先,定义元素类

DelayQueue只能添加(offer/put/add)实现了Delayed接口的对象,意思是说我们不能想往DelayQueue里添加什么就添加什么,不能添加int、也不能添加String进去,必须添加实现了Delayed接口的类对象,如下:

/**
 *  compareTo 方法必须提供与 getDelay 方法一致的排序
 */
class MyDelayedTask implements Delayed{

    private String name ;
    private long start = System.currentTimeMillis();
    private long time ;

    public MyDelayedTask(String name,long time) {
        this.name = name;
        this.time = time;
    }

    /**
     * 需要实现的接口,获得延迟时间   用过期时间-当前时间
该方法主要是判断消息是否到期(是否可以被读取出来)的依据。当返回负数,说明消息已到期,此时消息就可以被读取出来了。
     * @param unit
     * @return
     */
    @Override
    public long getDelay(TimeUnit unit) {
        return unit.convert((start+time) - System.currentTimeMillis(),TimeUnit.MILLISECONDS);
    }

    /**
     * 用于延迟队列内部比较排序   当前时间的延迟时间 - 比较对象的延迟时间
该方法主要在往DelayQueue里面加入数据会执行,根据此方法的返回值判断数据应该排在哪个位置。排得越前,越先被消费
     * @param o
     * @return
     */
    @Override
    public int compareTo(Delayed o) {
        MyDelayedTask o1 = (MyDelayedTask) o;
        return (int) (this.getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS));
    }

    @Override
    public String toString() {
        return "MyDelayedTask{" +
                "name='" + name + '\'' +
                ", time=" + time +
                '}';
    }
}

其中,compareTo 方法 getDelay 方法 就是Delayed接口的方法,我们必须实现,而且按照JAVASE文档,compareTo 方法必须提供与 getDelay 方法一致的排序,也就是说compareTo方法里可以按照getDelay方法的返回值大小排序,即在compareTo方法里比较getDelay方法返回值大小。

优点:java自带,轻量级,使用简单

缺点:存储内存中,服务器重启会造成数据丢失,可配合redis使用。当然了,如果数量庞大的,推荐使用mq消息中间件实现

main方法测试


定义一个DelayQueue,添加几个元素,while循环获取元素:

 private static DelayQueue delayQueue  = new DelayQueue();
    public static void main(String[] args) throws InterruptedException {

        new Thread(new Runnable() {
            @Override
            public void run() {

                delayQueue.offer(new MyDelayedTask("task1",10000));
                delayQueue.offer(new MyDelayedTask("task2",3900));
                delayQueue.offer(new MyDelayedTask("task3",1900));
                delayQueue.offer(new MyDelayedTask("task4",5900));
                delayQueue.offer(new MyDelayedTask("task5",6900));
                delayQueue.offer(new MyDelayedTask("task6",7900));
                delayQueue.offer(new MyDelayedTask("task7",4900));

            }
        }).start();

        while (true) {
            Delayed take = delayQueue.take();
            System.out.println(take);
        }
    }

执行结果:

MyDelayedTask{name='task3', time=1900}
MyDelayedTask{name='task2', time=3900}
MyDelayedTask{name='task7', time=4900}
MyDelayedTask{name='task4', time=5900}
MyDelayedTask{name='task5', time=6900}
MyDelayedTask{name='task6', time=7900}
MyDelayedTask{name='task1', time=10000}

再模拟一个电商系统订单的自动取消:

/**
 * 模拟一个使用DelayQueue的场景
 * 这里模拟的是订单下达之后,如果一直都还没支付,也就是停留在创建状态的话,就将其改成取消状态。
 *
 * @author lanjerry
 * @date 2019/2/14 15:28
 */
public class TestDelayQueue {

    /**
     * 初始化延迟队列
     */
    static DelayQueue<Order> queue = new DelayQueue<>();

    public static void main(String[] args) throws InterruptedException {

        //加入订单DD2019021401
        producer("DD2019021401");

        //停顿5秒,方便测试效果
        Thread.sleep(5000);

        //加入订单DD2019021402
        producer("DD2019021402");

        //执行消费
        consumer();
    }

    /**
     * 生产者
     *
     * @param orderNo 订单编号
     * @author lanjerry
     * @date 2019/2/14 15:35
     */
    private static void producer(String orderNo) {
        Order order = new Order();
        order.setOrderNo(orderNo);
        order.setStatus("待付款");
        order.setCreatedTime(LocalDateTime.now());
        queue.add(order);
        System.out.println(String.format("时间:%s,订单:%s加入队列", order.getCreatedTime().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")), order.getOrderNo()));
    }

    /**
     * 消费者
     *
     * @author lanjerry
     * @date 2019/2/14 15:36
     */
    private static void consumer() {
        try {
            while (true) {
                Order order = queue.take();
                order.setStatus("已取消");
                System.out.println(String.format("时间:%s,订单:%s已过期", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")), order.getOrderNo()));
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

运行结果:

                      

修改为多线程版:

    public static void main(String[] args) throws InterruptedException {

        //创建生产者线程
        Thread producerThread = new Thread(() -> {
            for (int i = 1; i <= 20; i++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                producer("DD20190214" + i);
            }
        });
        producerThread.start();

        //创建消费者线程
        Thread consumerThread = new Thread(() -> {
            consumer();
        });
        consumerThread.start();
    }

运行结果:

                                            

image.png

总结:

现功能时的选择很重要,如果你的系统所处理的数据量不是很大,我觉得队列和缓存很适合你,这样你可以对消息的传递更加了解,但你使用MQ,kafka的中间件时,你会发现使用起来更加轻松,但对于数据量大的系统来说,中间件是最好的选择,在这个大数据的时代,高并发,多线程,分布式会越来越重要

数据量小推荐使用:DelayQueue+redis

数据量大推荐使用:RabbitMQ

引用

发布了91 篇原创文章 · 获赞 11 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/hjing123/article/details/102892594