完整实现-通过DelayQueue实现延时任务

打印 上一主题 下一主题

主题 906|帖子 906|积分 2718

实现延时任务有很多的方法,网上关于延时任务的实现的文章已经不少了。比如:实现延时任务的10种方法等等。但是这些文章基本上都是将方法大概的列举一下,给出部分示例代码,对于有经验的老程序员可能一看就知道该怎么去把它实现完整,但是对于初学者来说不够友好。所以,我打算写一个系列的文章,详细的给出每种延时任务的实现方法、完整实现代码,以及工作原理,欢迎并期待大家关注我
小概念:什么是延时任务?举个例子:你买了一张火车票,必须在30分钟之内付款,否则该订单被自动取消。订单30分钟不付款自动取消,这个任务就是一个延时任务。
一、DelayQueue的应用原理

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

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

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

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

了解了DelayQueue的特点之后,我们就可以利用它来实现延时任务了,实现java.util.concurrent.Delayed接口。
  1. import org.jetbrains.annotations.NotNull;
  2. import java.text.SimpleDateFormat;
  3. import java.util.Date;
  4. import java.util.concurrent.Delayed;
  5. import java.util.concurrent.TimeUnit;
  6. /**
  7. * 延时订单任务
  8. */
  9. public class OrderDelayObject implements Delayed {
  10.   private String name;
  11.   private long delayTime;   //延时时间
  12.   //实际业务中这里传订单信息对象,我这里只做demo,所以使用字符串了
  13.   private String order;
  14.   public OrderDelayObject(String name, long delayTime, String order) {
  15.     this.name = name;
  16.     //延时时间加上当前时间
  17.     this.delayTime = System.currentTimeMillis() + delayTime;
  18.     this.order = order;
  19.   }
  20.   //获取延时任务的倒计时时间
  21.   @Override
  22.   public long getDelay(TimeUnit unit) {
  23.     long diff = delayTime - System.currentTimeMillis();
  24.     return unit.convert(diff, TimeUnit.MILLISECONDS);
  25.   }
  26.   //延时任务队列,按照延时时间元素排序,实现Comparable接口
  27.   @Override
  28.   public int compareTo(@NotNull Delayed obj) {
  29.     return Long.compare(this.delayTime, ((OrderDelayObject) obj).delayTime);
  30.   }
  31.   @Override
  32.   public String toString() {
  33.     Date date = new Date(delayTime);
  34.     SimpleDateFormat sd = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
  35.     return "\nOrderDelayObject:{"
  36.             + "name=" + name
  37.             + ", time=" + sd.format(date)
  38.             + ", order=" + order
  39.             + "}";
  40.   }
  41. }
复制代码

  • 上文类中的order为订单信息对象,在实际的业务开发过程中应该是传递订单信息,用于取消订单业务的实现(订单30分钟不付款自动取消)。
  • Delayed接口继承自 Comparable接口,所以需要实现compareTo方法,用于延时任务在队列中按照“延时时间”进行排序。
  • getDelay方法是Delayed接口方法,实现该方法提供获取延时任务的倒计时时间
三、订单处理

首先我们需要一个容器,永久保存延时任务队列,如果是Spring开发环境我们可以这样做。
  1. @Bean("orderDelayQueue")
  2. public DelayQueue<OrderDelayObject> orderDelayQueue(){
  3.     return new DelayQueue<OrderDelayObject>();
  4. }
复制代码
当用户下单的时候,将订单下单任务放入延时队列
  1. @Resource
  2. private DelayQueue<OrderDelayObject> orderDelayQueue;
  3. //发起订单下单的时候将订单演示对象放入orderDelayQueue
  4. orderDelayQueue.add(
  5.         new OrderDelayObject(
  6.                 "订单延时取消任务",
  7.                 30 * 60 * 1000,    //延时30分钟
  8.                 "延时任务订单对象信息"
  9.         )
  10. );
复制代码
系统内开启一个线程,不断的从队列中获取消息,获取到之后对延时消息进行处理。DelayQueue的take方法从队列中获取延时任务对象,如果队列元素数量为0,或者没有到达“延时时间的任务”,该线程会被阻塞。
  1. @Component
  2. public class DelayObjectConsumer  implements InitializingBean {
  3.   @Resource
  4.   private DelayQueue<OrderDelayObject> orderDelayQueue;
  5.   @Override
  6.   public void afterPropertiesSet() throws Exception {
  7.     while (true) {
  8.       OrderDelayObject task = orderDelayQueue.take();
  9.       System.out.println(task.toString());
  10.       System.out.println(task.getOrder());
  11.       //根据order订单信息,去查询该订单的支付信息
  12.       //如果用户没有进行支付,将订单从数据库中关闭
  13.       //如果订单并发量比较大,这里可以考虑异步或线程池的方式进行处理
  14.     }
  15.   }
  16. }
复制代码
需要说明的是,这里的while-true循环的延时任务处理时顺序执行的,在订单并发量比较大的时候,需要考虑异步处理的方式完成订单的关闭操作。我之前写作一个SpringBoot的可观测、易配置的线程池开源项目,可能会对你有帮助,源代码地址:https://gitee.com/hanxt/zimug-monitor-threadpool
经过我的测试,放入orderDelayQueue的延时任务,在半小时之后得到正确的执行处理。说明我们的实现是正确的。
四、优缺点

使用DelayQueue实现延时任务非常简单,而且简便,全部都是标准的JDK代码实现,不用引入第三方依赖(不依赖redis实现、消息队列实现等),非常的轻量级。
它的缺点就是所有的操作都是基于应用内存的,一旦出现应用单点故障,可能会造成延时任务数据的丢失。如果订单并发量非常大,因为DelayQueue是无界的,订单量越大,队列内的对象就越多,可能造成OOM的风险。所以使用DelayQueue实现延时任务,只适用于任务量较小的情况。
欢迎关注我的公告号:字母哥杂谈,回复003赠送作者专栏《docker修炼之道》的PDF版本,30余篇精品docker文章。字母哥博客:zimug.com

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

干翻全岛蛙蛙

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表