java通过DelayQueue实现延时任务

 更新时间:2023年07月24日 08:34:11   作者:字母哥哥  
本文主要介绍了java通过DelayQueue实现延时任务,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

实现延时任务有很多的方法,网上关于延时任务的实现的文章已经不少了。比如:实现延时任务的10种方法等等。但是这些文章基本上都是将方法大概的列举一下,给出部分示例代码,对于有经验的老程序员可能一看就知道该怎么去把它实现完整,但是对于初学者来说不够友好。

小概念:什么是延时任务?举个例子:你买了一张火车票,必须在30分钟之内付款,否则该订单被自动取消。「订单30分钟不付款自动取消,这个任务就是一个延时任务。」

一、DelayQueue的应用原理

DelayQueue是一个无界的BlockingQueue的实现类,用于放置实现了Delayed接口的对象,其中的对象只能在其到期时才能从队列中取走。

  • BlockingQueue即阻塞队列,java提供的面向多线程安全的队列数据结构,当队列内元素数量为0的时候,试图从队列内获取元素的线程将被阻塞或者抛出异常。
  • 这里的“无界”队列,是指队列的元素数量不存在上限,队列的容量会随着元素数量的增加而扩容。

DelayQueue实现了BlockingQueue接口,所以具有无界、阻塞的特点,除此之外它自己的核心特点就是:

  • 「放入该队列的延时任务对象,只要到达延时时间之后才能被取到」。
  • DelayQueue 不接收null元素
  • 「DelayQueue 只接受那些实现了java.util.concurrent.Delayed接口的对象」

二、订单延时任务的实现

了解了DelayQueue的特点之后,我们就可以利用它来实现延时任务了,实现java.util.concurrent.Delayed接口。

import org.jetbrains.annotations.NotNull;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;
/**
 * 延时订单任务
 */
public class OrderDelayObject implements Delayed {
  private String name;
  private long delayTime;   //延时时间
  //实际业务中这里传订单信息对象,我这里只做demo,所以使用字符串了
  private String order;
  public OrderDelayObject(String name, long delayTime, String order) {
    this.name = name;
    //延时时间加上当前时间
    this.delayTime = System.currentTimeMillis() + delayTime;
    this.order = order;
  }
  //获取延时任务的倒计时时间
  @Override
  public long getDelay(TimeUnit unit) {
    long diff = delayTime - System.currentTimeMillis();
    return unit.convert(diff, TimeUnit.MILLISECONDS);
  }
  //延时任务队列,按照延时时间元素排序,实现Comparable接口
  @Override
  public int compareTo(@NotNull Delayed obj) {
    return Long.compare(this.delayTime, ((OrderDelayObject) obj).delayTime);
  }
  @Override
  public String toString() {
    Date date = new Date(delayTime);
    SimpleDateFormat sd = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    return "\nOrderDelayObject:{"
            + "name=" + name
            + ", time=" + sd.format(date)
            + ", order=" + order
            + "}";
  }
} 

上文类中的order为订单信息对象,在实际的业务开发过程中应该是传递订单信息,用于取消订单业务的实现(订单30分钟不付款自动取消)。

Delayed接口继承自 Comparable接口,所以需要实现compareTo方法,用于延时任务在队列中按照“延时时间”进行排序。

getDelay方法是Delayed接口方法,实现该方法提供获取延时任务的倒计时时间

三、订单处理

首先我们需要一个容器,永久保存延时任务队列,如果是Spring开发环境我们可以这样做。

@Bean("orderDelayQueue")
public DelayQueue<OrderDelayObject> orderDelayQueue(){
    return new DelayQueue<OrderDelayObject>();
}

当用户下单的时候,将订单下单任务放入延时队列

@Resource
private DelayQueue<OrderDelayObject> orderDelayQueue;
//发起订单下单的时候将订单演示对象放入orderDelayQueue
orderDelayQueue.add(
        new OrderDelayObject(
                "订单延时取消任务",
                30 * 60 * 1000,    //延时30分钟
                "延时任务订单对象信息"
        )
);

系统内开启一个线程,不断的从队列中获取消息,获取到之后对延时消息进行处理。DelayQueue的take方法从队列中获取延时任务对象,如果队列元素数量为0,或者没有到达“延时时间的任务”,该线程会被阻塞。

@Component
public class DelayObjectConsumer<OrderDelayObject> implements InitializingBean {
  @Resource
  private DelayQueue<OrderDelayObject> orderDelayQueue;
  @Override
  public void afterPropertiesSet() throws Exception {
    new Thread(() -> {
      while (true) {
        OrderDelayObject task = orderDelayQueue.take();
        System.out.println(task.toString());
        System.out.println(task.getOrder());
        //根据order订单信息,去查询该订单的支付信息
        //如果用户没有进行支付,将订单从数据库中关闭
        //如果订单并发量比较大,这里可以考虑异步或线程池的方式进行处理
      }
    }).start();
  }
}

需要说明的是,这里的while-true循环的延时任务处理是顺序执行的,在订单并发量比较大的时候,需要考虑异步处理的方式完成订单的关闭操作。我之前写过一个SpringBoot的可观测、易配置的线程池开源项目,可能会对你有帮助,源代码地址:gitee.com/hanxt/zimug-monitor-threadpool

经过我的测试,放入orderDelayQueue的延时任务,在半小时之后得到正确的执行处理。说明我们的实现是正确的。

四、优缺点

  • 使用DelayQueue实现延时任务非常简单,而且简便,全部都是标准的JDK代码实现,不用引入第三方依赖(不依赖redis实现、消息队列实现等),非常的轻量级。
  • 它的缺点就是所有的操作都是基于应用内存的,一旦出现应用单点故障,可能会造成延时任务数据的丢失。如果订单并发量非常大,因为DelayQueue是无界的,订单量越大,队列内的对象就越多,可能造成OOM的风险。所以使用DelayQueue实现延时任务,只适用于任务量较小的情况。

到此这篇关于java通过DelayQueue实现延时任务的文章就介绍到这了,更多相关java DelayQueue延时任务内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • myeclipse安装Spring Tool Suite(STS)插件的方法步骤

    myeclipse安装Spring Tool Suite(STS)插件的方法步骤

    这篇文章主要介绍了myeclipse安装Spring Tool Suite(STS)插件的方法步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-08-08
  • Java数据结构与算法之双向链表、环形链表及约瑟夫问题深入理解

    Java数据结构与算法之双向链表、环形链表及约瑟夫问题深入理解

    这篇文章主要介绍了Java数据结构与算法之双向链表、环形链表及约瑟夫问题深入理解,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-09-09
  • idea新建mapper.xml文件详细步骤如:mybatis-config

    idea新建mapper.xml文件详细步骤如:mybatis-config

    这篇文章主要介绍了idea新建xml模板设置,例如:mybatis-config,本文分步骤通过图文并茂的形式给大家介绍的非常详细,需要的朋友可以参考下
    2023-07-07
  • SpringCloud 服务注册IP错误的解决

    SpringCloud 服务注册IP错误的解决

    这篇文章主要介绍了SpringCloud 服务注册IP错误的解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-07-07
  • java手动实现常见数据结构的示例代码

    java手动实现常见数据结构的示例代码

    本文介绍了Java中常用数据结构的特点和Java实现,包括数组、动态数组、链表、栈、队列、哈希表、树、堆、图、集合、双向队列以及自定义链表,帮助开发者选择合适的数据结构以提升代码效率,感兴趣的朋友一起看看吧
    2025-02-02
  • Java中的锁与锁的状态升级详细解读

    Java中的锁与锁的状态升级详细解读

    这篇文章主要介绍了Java中的锁与锁的状态升级详细解读,Java 1.6以后官方针对锁的优化,主要是增加了两种新的锁:偏向锁和轻量级锁,再加上本身重量级锁,那么锁基本上可以大致分为这三种,它们之间的区别主要是体现在等待时间上面,需要的朋友可以参考下
    2024-01-01
  • 排序算法图解之Java快速排序的分步刨析

    排序算法图解之Java快速排序的分步刨析

    快速排序是通过一趟排序将要排序的数据分割为独立的两个部分,一部分的所有数据比另外一部分的所有数据要小,然后按照此方法对这两部分分别进行快速排序,整个过程可以递归进行,以此达到整个数据变成有序序列。本文通过示例讲解了快速排序的实现,需要的可以参考一下
    2022-11-11
  • Java中如何模拟HTTP请求并验证功能

    Java中如何模拟HTTP请求并验证功能

    要模拟HTTP请求并验证功能,你可以使用Spring Boot提供的MockMvc工具,它允许我们在没有实际启动HTTP服务器的情况下测试Spring MVC控制器,下面给大家分享如何模拟HTTP请求并验证功能,感兴趣的朋友一起看看吧
    2024-05-05
  • JDK8接口的默认与静态方法-接口与抽象类的区别详解

    JDK8接口的默认与静态方法-接口与抽象类的区别详解

    这篇文章主要介绍了JDK8接口的默认与静态方法-接口与抽象类的区别详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,,需要的朋友可以参考下
    2019-06-06
  • Spring cloud Feign 深度学习与应用详解

    Spring cloud Feign 深度学习与应用详解

    这篇文章主要介绍了Spring cloud Feign 深度学习与应用详解,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2019-06-06

最新评论