完整实现-通过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 implements InitializingBean {
@Resource
private DelayQueue<OrderDelayObject> orderDelayQueue;
@Override
public void afterPropertiesSet() throws Exception {
while (true) {
OrderDelayObject task = orderDelayQueue.take();
System.out.println(task.toString());
System.out.println(task.getOrder());
//根据order订单信息,去查询该订单的支付信息
//如果用户没有进行支付,将订单从数据库中关闭
//如果订单并发量比较大,这里可以考虑异步或线程池的方式进行处理
}
}
}
需要说明的是,这里的while-true循环的延时任务处理时顺序执行的,在订单并发量比较大的时候,需要考虑异步处理的方式完成订单的关闭操作。我之前写作一个SpringBoot的可观测、易配置的线程池开源项目,可能会对你有帮助,源代码地址:https://gitee.com/hanxt/zimug-monitor-threadpool
经过我的测试,放入orderDelayQueue的延时任务,在半小时之后得到正确的执行处理。说明我们的实现是正确的。
四、优缺点
使用DelayQueue实现延时任务非常简单,而且简便,全部都是标准的JDK代码实现,不用引入第三方依赖(不依赖redis实现、消息队列实现等),非常的轻量级。
它的缺点就是所有的操作都是基于应用内存的,一旦出现应用单点故障,可能会造成延时任务数据的丢失。如果订单并发量非常大,因为DelayQueue是无界的,订单量越大,队列内的对象就越多,可能造成OOM的风险。所以使用DelayQueue实现延时任务,只适用于任务量较小的情况。
欢迎关注我的公告号:字母哥杂谈,回复003赠送作者专栏《docker修炼之道》的PDF版本,30余篇精品docker文章。字母哥博客:zimug.com
完整实现-通过DelayQueue实现延时任务的更多相关文章
- 延时任务-基于redis zset的完整实现
所谓的延时任务给大家举个例子:你买了一张火车票,必须在30分钟之内付款,否则该订单被自动取消.订单30分钟不付款自动取消,这个任务就是一个延时任务. 我之前已经写过2篇关于延时任务的文章: <完 ...
- springboot执行延时任务-DelayQueue的使用
DelayQueue简介 在很多场景我们需要用到延时任务,比如给客户异步转账操作超时后发通知告知用户,还有客户下单后多长时间内没支付则取消订单等等,这些都可以使用延时任务来实现. jdk中DelayQ ...
- 阿里面试官让我实现一个线程安全并且可以设置过期时间的LRU缓存,我蒙了!
目录 1. LRU 缓存介绍 2. ConcurrentLinkedQueue简单介绍 3. ReadWriteLock简单介绍 4.ScheduledExecutorService 简单介绍 5. ...
- Java并发编程原理与实战三十六:阻塞队列&消息队列
一.阻塞队列 1.阻塞队列BlockingQueue ---->可以理解成生产者消费者的模式---->消费者要等待到生产者生产出来产品.---->而非阻塞队列ConcurrentLi ...
- 细说并发5:Java 阻塞队列源码分析(下)
上一篇 细说并发4:Java 阻塞队列源码分析(上) 我们了解了 ArrayBlockingQueue, LinkedBlockingQueue 和 PriorityBlockingQueue,这篇文 ...
- Java进阶知识点:不要只会写synchronized - JDK十大并发编程组件总结
一.背景 提到Java中的并发编程,首先想到的便是使用synchronized代码块,保证代码块在并发环境下有序执行,从而避免冲突.如果涉及多线程间通信,可以再在synchronized代码块中使用w ...
- Java进阶知识点7:不要只会写synchronized - JDK十大并发编程组件总结
一.背景 提到Java中的并发编程,首先想到的便是使用synchronized代码块,保证代码块在并发环境下有序执行,从而避免冲突.如果涉及多线程间通信,可以再在synchronized代码块中使用w ...
- Java基础教程(24)--集合
一.Java集合框架 集合,有时也称为容器,是一个用来存储和管理多个元素的对象.Java中的集合框架定义了一套规范,用来表示和操作集合,使具体操作与实现细节解耦.集合框架都包含下列内容: 接口:这 ...
- java里面的队列
非阻塞无界队列 ConcurrentLinkedQueue public static void main(String[] args) throws InterruptedException { ...
随机推荐
- Python的关键字参数与斜杠“/”
Python3.8 新增了一种语法,可以使用斜杠 / 占据一个参数的位置,表示在此之前的参数都只接受位置参数的传参形式. 例如,对以下函数声明: def func(a, b, /, c, d, *, ...
- SAP APO-供应链监控
供应链监控(SCM)是一种有效的工具,可以为供应商和购买者做出明智的决策. 供应链流程中的关键人物基于供应链监控做出决策. 供应链包括两个工具- 库存需求清单MD04 物流信息系统(LIS) 事务MD ...
- 缤纷多彩的WPF样式框架,开源项目
下面介绍的四种主流样式框架(最近项目需要,所以了解了一些),在Nuget及Github均可以找到~ 首推样式框架MahApp.Metro 再推样式框架ModernUI 三推样式框架MaterialDe ...
- UiPath程序设计文档
1. [RPA之家]添加数据列UiPath.Core.Activities.AddDataColumn 链接: https://pan.baidu.com/s/1RRMw4voqJru-fJSoC3W ...
- UiPath文本操作Get OCR Text的介绍和使用
一.Get OCR Text操作的介绍 使用OCR屏幕抓取方法从指示的UI元素或图像中提取字符串及其信息.执行屏幕抓取操作时,还可以自动生成此活动以及容器.默认情况下,使用Google OCR引擎. ...
- 6G显卡显存不足出现CUDA Error:out of memory解决办法
从6月初开始,6G显存的显卡开始出现CUDA Error:out of memory的问题,这是因为dag文件一直在增加,不过要增加到6G还需要最少两年的时间. 现在出现问题的原因是1.内核太古老 ...
- JAVA设计模式总结—建造者模式
建造者模式 模式动机与定义 首先建造者模式的动机是为了创建复杂对象,简化传统的创建方法,提高创建的效率和可读性. 像图中的这个例子,用户的需求是驾驶一辆汽车,但是对于用户来说是不需要了解汽车装 ...
- NC16649 [NOIP2005]校门外的树
NC16649 [NOIP2005]校门外的树 题目 题目描述 某校大门外长度为 \(L\) 的马路上有一排树,每两棵相邻的树之间的间隔都是 \(1\) 米.我们可以把马路看成一个数轴,马路的一端在数 ...
- 选择结构-穿透的switch语句和循环结构-循环概述
case的穿透性 在switch语句中,如果case的后面不写break,将出现穿透现象,也就是不会在判断下一个case的值,直接向后运 行,直到遇到break,或者整体switch结束 publi ...
- 优雅哥学 Webpack - 01 - Webpack 5 快速体验
程序员优雅哥简介:十年程序员,呆过央企外企私企,做过前端后端架构.分享vue.Java等前后端技术和架构 本文摘要:主要讲解 webpack 5 初体验.从webpack 5 初识到便写代码.优雅哥将 ...