当前位置: 首页 > 后端技术 > Java

完整实现——延迟任务通过DelayQueue实现

时间:2023-04-01 17:20:20 Java

延迟任务的实现方式有很多种。关于延迟任务的实现,网上已经有很多文章了。例如:实现延迟任务的10种方法等等。但是这些文章基本上都列出了方法,并给出了一些示例代码。有经验的程序员可能一眼就知道如何实现,但对初学者不够友好。因此,我打算写一个系列文章,详细介绍每个延迟任务的实现方法、完整的实现代码和工作原理。欢迎并期待您的关注。小概念:什么是延迟任务?例如:您购买了火车票,必须在30分钟内付款,否则订单将自动取消。30分钟内未付款,订单将自动取消。此任务是延迟任务。1、DelayQueue的应用原理DelayQueue是一个无界的BlockingQueue实现类,用于放置实现了Delayed接口的对象,对象只有过期才能从队列中取出。BlockingQueue是阻塞队列,java提供的一种多线程安全的队列数据结构。当队列中的元素个数为0时,试图从队列中获取元素的线程将被阻塞或抛出异常。这里的“无界”队列是指队列中的元素个数没有上限,队列的容量会随着元素个数的增加而扩大。DelayQueue实现了BlockingQueue接口,因此具有无界和阻塞的特点。另外,它的核心特点是:放入队列的延迟任务对象只有在延迟时间到达后才能取出。DelayQueue不接受null元素DelayQueue只接受实现了java.util.concurrent.Delayed接口的对象2.顺序延迟任务的实现了解了DelayQueue的特性后,我们就可以利用它来实现延迟任务了。实现java.util.concurrent.Delayed接口。importorg.jetbrains.annotations.NotNull;importjava.text.SimpleDateFormat;importjava.util.Date;importjava.util.concurrent.Delayed;importjava.util.concurrent.TimeUnit;/***延迟订单任务*/publicclassOrderDelayObjectimplementsDelayed{私有字符串名称;私人长延迟时间;//延时时间//在实际业务中,这里传递的是订单信息对象。我这里只做demo,所以使用stringprivateString命令;publicOrderDelayObject(Stringname,longdelayTime,Stringorder){this.name=name;//延迟时间加上当前时间this.delayTime=System.currentTimeMillis()+delayTime;this.order=订单;}//获取延时任务倒计时时间@OverridepubliclonggetDelay(TimeUnitunit){longdiff=delayTime-System.currentTimeMillis();返回unit.convert(diff,TimeUnit.MILLISECONDS);}//延时任务队列,按延时元素排序,实现Comparable接口@OverridepublicintcompareTo(@NotNullDelayedobj){returnLong.compare(this.delayTime,((OrderDelayObject)obj).delayTime);}@OverridepublicStringtoString(){Datedate=newDate(del时间);SimpleDateFormatsd=newSimpleDateFormat("yyyy-MM-ddHH:mm:ss");返回"\nOrderDelayObject:{"+"name="+name+",time="+sd.format(date)+",order="+order+"}";}}上面类中的订单为订单信息对象,在实际业务开发过程中,需要传递订单信息,实现订单业务的取消(订单30分钟未付款自动取消)。Delayed接口继承自Comparable接口,因此需要实现compareTo方法,根据“延迟时间”对队列中的延迟任务进行排序。getDelay方法是Delayed接口的方法。实现该方法提供获取延迟任务的倒计时时间。3.订单处理首先我们需要一个容器来永久存放延迟任务队列。我们可以在Spring开发环境中进行。@Bean("orderDelayQueue")publicDelayQueueorderDelayQueue(){returnnewDelayQueue();}当用户下单时,将下单任务放入延迟队列@ResourceprivateDelayQueueorderDelayQueue;//下单时,将订单演示对象放入orderDelayQueueorderDelayQueue.add(newOrderDelayObject("订单延迟取消任务",30*60*1000,//延迟30分钟"延迟任务订单对象信息"));系统中开启一个线程不断从队列中获取消息,获取到后对延迟的消息进行处理。DelayQueue的take方法从队列中获取延迟任务对象。如果队列元素个数为0,或者没有达到“延迟时间任务”,线程就会被阻塞。@ComponentpublicclassDelayObjectConsumerimplementsInitializingBean{@ResourceprivateDelayQueueorderDelayQueue;@OverridepublicvoidafterPropertiesSet()throwsException{newThread(()->{while(true){OrderDelayObjecttask=orderDelay(Queue);System.out.println(task.toString());System.out.println(task.getOrder());//根据订单信息,查询订单的支付信息//如果用户不支付,会从数据库中关闭订单//如果订单并发比较大,这里可以考虑异步或者线程池处理}}).start();}}需要注意的是,这里的while-true循环的延迟任务是按顺序进行处理的。当订单并发量比较大时,需要考虑异步处理的方式来完成订单平仓操作。之前写过一个SpringBoot的可观察且易于配置的线程池开源项目,希望对你有所帮助。源码地址:https://gitee.com/hanxt/zimug...经过我的测试,放入orderDelayQueue中,延迟任务会在半小时后正确执行。证明我们的实现是正确的。4.优缺点使用DelayQueue实现延时任务非常简单方便,都是通过标准的JDK代码实现的,没有引入第三方依赖(不依赖redis实现,消息队列实现等),非常轻巧。它的缺点是所有操作都基于申请内存。一旦出现应用单点故障,可能会导致延迟任务数据丢失。如果订单的并发量很大,由于DelayQueue是无界的,订单量越大,队列中的对象越多,可能会造成OOM的风险。因此,使用DelayQueue实现延迟任务只适用于任务量不多的情况。欢迎关注我的公告号:字母哥杂谈,回复003送作者专栏《docker修炼之道》30多篇优质docker文章PDF版。Antetokounmpo博客:zimug.com