• Java延迟队列——DelayQueue


    一、DelayQueue的定义

    public class DelayQueue<E extends Delayed> extends AbstractQueue<E> implements BlockingQueue<E>
    
    • 1

    DelayQueue是一个无界的BlockingQueue,是线程安全的(无界指的是队列的元素数量不存在上限,队列的容量会随着元素数量的增加而扩容,阻塞队列指的是当队列内元素数量为0的时候,试图从队列内获取元素的线程将被阻塞或者抛出异常)

    以上是阻塞队列的特点,而延迟队列还拥有自己如下的特点:

    DelayQueue中存入的必须是实现了Delayed接口的对象(Delayed定义了一个getDelay的方法,用来判断排序后的元素是否可以从Queue中取出,并且Delayed接口还继承了Comparable用于排序),插入Queue中的数据根据compareTo方法进行排序(DelayQueue的底层存储是一个PriorityQueue,PriorityQueue是一个可排序的Queue,其中的元素必须实现Comparable接口的compareTo方法),并通过getDelay方法返回的时间确定元素是否可以出队,只有小于等于0的元素(即延迟到期的元素)才能够被取出

    延迟队列不接收null元素

    二、DelayQueue的作用

    延迟队列的作用显然就是用于执行延时任务,如:

    1. 淘宝订单业务:下单之后如果三十分钟之内没有付款就自动取消订单。
    2. 饿了吗订餐通知:下单成功后60s之后给用户发送短信通知。
    3. 关闭空闲连接。服务器中,有很多客户端的连接,空闲一段时间之后需要关闭之。
    4. 缓存。缓存中的对象,超过了空闲时间,需要从缓存中移出。
    5. 任务超时处理。在网络协议滑动窗口请求应答式交互时,处理超时未响应的请求等。

    三、DelayQueue的实现

    定义延迟队列元素

    public class OrderDelayTask implements Delayed {
        
        private Long orderId;
        
        private long delayTime;
    
        public OrderDelayTask(Long orderId, long delayTime) {
            this.orderId = orderId;
            // 延迟时间加当前时间
            this.delayTime = System.currentTimeMillis() + delayTime;
        }
    
        // 获取任务剩余时间
        @Override
        public long getDelay(TimeUnit unit) {
            return unit.convert(delayTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
        }
    
        @Override
        public int compareTo(Delayed o) {
            return Long.compare(delayTime, ((OrderDelayTask) o).delayTime);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    定义延迟队列并交付容器管理

    @Bean("orderDelayQueue")
    public DelayQueue<OrderDelayTask> orderDelayQueue(){
        return new DelayQueue<OrderDelayTask>();
    }
    
    • 1
    • 2
    • 3
    • 4

    使用延迟队列

    @Autowired
    private DelayQueue<OrderDelayObject> orderDelayQueue;
    
    //发起订单下单的时候将订单演示对象放入orderDelayQueue
    orderDelayQueue.add(
            new OrderDelayTask(
                    "123456", // 订单id
                    30 * 60 * 1000, // 延迟时间:30分钟
            )
    );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    开启线程处理延迟任务

    @Component
    public class DelayTaskRunner<OrderDelayTask> implements InitializingBean {
        
        @Autowired
        private DelayQueue<OrderDelayTask> orderDelayQueue;
        
        @Override
        public void afterPropertiesSet() throws Exception {
        	new Thread(() -> {
                try {
                    while(true) {
                        OrderDelayTask task = orderDelayQueue.take();
                        // 当队列为null的时候,poll()方法会直接返回null, 不会抛出异常,但是take()方法会一直等待,因此会抛出一个InterruptedException类型的异常。(当阻塞方法收到中断请求的时候就会抛出InterruptedException异常)
                        Long orderId = task.getOrderId();
                        // 执行业务
                    }
                } catch (InterruptedException e) { 
                    // 因为是重写Runnable接口的run方法,子类抛出的异常要小于等于父类的异常。而在Runnable中run方法是没有抛异常的。所以此时是不能抛出InterruptedException异常。如果此时你只是记录日志的话,那么就是一个不负责任的做法,因为在捕获InterruptedException异常的时候自动的将是否请求中断标志置为了false。在捕获了InterruptedException异常之后,如果你什么也不想做,那么就将标志重新置为true,以便栈中更高层的代码能知道中断,并且对中断作出响应。
                	Thread.currentThread().interrupt();
                }
            }).start();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    四、DelayQueue实现延时任务的优缺点

    使用DelayQueue实现延时任务非常简单,而且简便,全部都是标准的JDK代码实现,不用引入第三方依赖(不依赖redis实现、消息队列实现等),非常的轻量级。

    它的缺点就是所有的操作都是基于应用内存的,一旦出现应用单点故障,可能会造成延时任务数据的丢失。如果订单并发量非常大,因为DelayQueue是无界的,订单量越大,队列内的对象就越多,可能造成OOM的风险。所以使用DelayQueue实现延时任务,只适用于任务量较小的情况。

  • 相关阅读:
    C++基础入门
    【2023最新版】DataGrip安装及使用教程
    大语言模型:Large Language Models Are Human-Level Prompt Engineers概述
    Causality
    House Of Force
    Mac 从源码安装wxWidgets 报错 fatal error: ‘tiff.h‘ file not found
    复盘归因,提高交付质量的秘诀
    Java EnumMap clear()具有什么功能呢?
    Linux系统编程-线程同步详解
    【ES】笔记-ES6模块化
  • 原文地址:https://blog.csdn.net/qq_25046827/article/details/126693681