定时任务应该这么玩
1|01.场景
在电商系统中会经常遇到这样一种场景,就是商品的定时上下架功能,总不能每次都手动执行吧,这个时候我们首先想到的就是利用定时任务来实现这个功能。
目前实现定时任务主要有以下几种方式:
-
JDK自带 :JDK自带的Timer以及JDK1.5+ 新增的ScheduledExecutorService;
-
第三方框架 :使用 Quartz、elastic-job、xxl-job 等开源第三方定时任务框架,适合分布式项目应用。该方式的缺点是配置复杂。
-
Spring :使用 Spring 提供的一个注解
@Schedule
,开发简单,使用比较方便。
本文博主主要向大家介绍Quartz框架和Spring定时任务的使用。
2|02.什么是Quartz
Quartz 是一个完全由 Java 编写的开源作业调度框架,为在 Java 应用程序中进行作业调度提供了简单却强大的机制。
Quartz 可以与 J2EE 与 J2SE 应用程序相结合也可以单独使用。
Quartz 允许程序开发人员根据时间的间隔来调度作业。
Quartz 实现了作业和触发器的多对多的关系,还能把多个作业与不同的触发器关联。
3|03.Quartz几个核心概念
在正式学习使用Quartz之前,我们需要了解几个有关Quartz的核心概念,方便我们后面学习
-
Job 表示一个工作,要执行的具体内容。此接口中只有一个方法,如下:
-
JobDetail 表示一个具体的可执行的调度程序,Job 是这个可执行程调度程序所要执行的内容,另外 JobDetail 还包含了这个任务调度的方案和策略。
-
Trigger 代表一个调度参数的配置,什么时候去调。
-
Scheduler 代表一个调度容器,一个调度容器中可以注册多个 JobDetail 和 Trigger。当 Trigger 与 JobDetail 组合,就可以被 Scheduler 容器调度了。
4|04.Quartz初体验
一、创建一个SpringBoot项目,pom.xml配置如下
二、创建一个Job(Job里面是要执行的具体内容)
三、执行Job
控制台输出
从输出结果我们可以看到此Job每隔3秒执行一次
有关概念
1、Job
job的一个 trigger 被触发后(稍后会讲到),execute() 方法会被 scheduler 的一个工作线程调用;传递给 execute() 方法的 JobExecutionContext 对象中保存着该 job 运行时的一些信息 ,执行 job 的 scheduler 的引用,触发 job 的 trigger 的引用,JobDetail 对象引用,以及一些其它信息。
2、JobDetail :
JobDetail 对象是在将 job 加入 scheduler 时,由客户端程序(你的程序)创建的。它包含 job 的各种属性设置,以及用于存储 job 实例状态信息的 JobDataMap
3、Trigger:
Trigger 用于触发 Job 的执行。当你准备调度一个 job 时,你创建一个 Trigger 的实例,然后设置调度相关的属性。Trigger 也有一个相关联的 JobDataMap,用于给 Job 传递一些触发相关的参数。Quartz 自带了各种不同类型的 Trigger,最常用的主要是 SimpleTrigger 和 CronTrigger。SimpleTrigger 主要用于一次性执行的 Job(只在某个特定的时间点执行一次),或者 Job 在特定的时间点执行,重复执行 N 次,每次执行间隔T个时间单位。CronTrigger 在基于日历的调度上非常有用,如“每个星期五的正午”,或者“每月的第十天的上午 10:15”等。
5|05.JobDetail详解
在定义一个Job时,我们需要实现Job接口,该接口只有一个execute
方法。
从上一节的案例中我们可以发现,我们通过Scheduler去执行Job,我们传给scheduler一个JobDetail实例,因为我们在创建JobDetail时,将要执行的job的类名传给了JobDetail,所以scheduler就知道了要执行何种类型的job。(这里利用了Java中的反射创建实例对象)每次当scheduler执行job时,在调用其execute(…)方法之前会创建该类的一个新的实例;执行完毕,对该实例的引用就被丢弃了,实例会被垃圾回收;这种执行策略带来的一个后果是,job必须有一个无参的构造函数(当使用默认的JobFactory时);另一个后果是,在job类中,不应该定义有状态的数据属性,因为在job的多次执行中,这些属性的值不会保留。
那么我们该如何给Job配置相关属性呢?答案就是通过JobDetail
5|1JobDataMap
JobDataMap实现了Map接口,可以存放键值对数据,在Job执行的时候,我们就可以通过JobExecutionContext获取到JobDataMap中的数据,如下
在job的执行过程中,可以从JobDataMap中取出数据,如下示例:
当然,如果你希望实现属性的自动注入,那么你可以使用下面的方法
给Job类加上get和set方法(属性名称要和JobDataMap中的key相同),那么JobDataMap中的值就是自动注入到Job中,不需要手动获取
6|06.Triggers详解
Trigger 用于触发 Job 的执行。当你准备调度一个 job 时,你创建一个 Trigger 的实例,然后设置调度相关的属性。所有类型的trigger都有TriggerKey这个属性,表示trigger的身份;除此之外,trigger还有很多其它的公共属性。这些属性,在构建trigger的时候可以通过TriggerBuilder设置。
6|1triggers公共属性
- jobKey属性:当trigger触发时被执行的job的身份;
- startTime属性:设置trigger第一次触发的时间;该属性的值是java.util.Date类型,表示某个指定的时间点;有些类型的trigger,会在设置的startTime时立即触发,有些类型的trigger,表示其触发是在startTime之后开始生效。比如,现在是1月份,你设置了一个trigger–“在每个月的第5天执行”,然后你将startTime属性设置为4月1号,则该trigger第一次触发会是在几个月以后了(即4月5号)。
- endTime属性:表示trigger失效的时间点。比如,”每月第5天执行”的trigger,如果其endTime是7月1号,则其最后一次执行时间是6月5号。
6|2优先级(priority)
如果你的trigger很多(或者Quartz线程池的工作线程太少),Quartz可能没有足够的资源同时触发所有的trigger;这种情况下,你可能希望控制哪些trigger优先使用Quartz的工作线程,要达到该目的,可以在trigger上设置priority属性。比如,你有N个trigger需要同时触发,但只有Z个工作线程,优先级最高的Z个trigger会被首先触发。如果没有为trigger设置优先级,trigger使用默认优先级,值为5;priority属性的值可以是任意整数,正数、负数都可以。
注意:只有同时触发的trigger之间才会比较优先级。10:59触发的trigger总是在11:00触发的trigger之前执行。
注意:如果trigger是可恢复的,在恢复后再调度时,优先级与原trigger是一样的。
6|3错过触发(misfire Instructions)
trigger还有一个重要的属性misfire;如果scheduler关闭了,或者Quartz线程池中没有可用的线程来执行job,此时持久性的trigger就会错过(miss)其触发时间,即错过触发(misfire)。不同类型的trigger,有不同的misfire机制。它们默认都使用“智能机制(smart policy)”,即根据trigger的类型和配置动态调整行为
6|4Simple Trigger
SimpleTrigger简单点说,就是在具体的时间点执行一次,或者在具体的时间点执行,并且以指定的间隔重复执行若干次。类似于闹钟,你定了一个周末早晨7点的闹钟,这个闹钟会在周末早上7点准时响起。闹钟还有个功能就是过5分钟之后再响一次,这对应着指定的间隔重复执行若干次。
1、指定时间开始触发,不重复:
2、指定时间触发,每隔2秒执行一次,重复5次:
3、1分钟以后开始触发,仅执行一次:
4、立即触发,每隔2秒钟执行一次,直到2020-12-19 13:20:00
5、在13:00触发,然后每2小时重复一次:
6|5SimpleTrigger Misfire
misfire:被错过的执行任务策略
6|6CronTrigger
CronTrigger通常比Simple Trigger更有用,如果你需要在指定日期执行某项任务,使用CronTrigger就非常方便,比如如果你想在每月的15号给会员发放优惠券,或者每周五中午12点统计用户本周使用产品时长。
cron
表达式是一个字符串,该字符串由 6 个空格分为 7 个域,每一个域代表一个时间含义。 通常定义 “年” 的部分可以省略,实际常用的 Cron 表达式由前 6 部分组成。格式如下
域 | 是否必填 | 值以及范围 | 通配符 |
---|---|---|---|
秒 | 是 | 0-59 | , - * / |
分 | 是 | 0-59 | , - * / |
时 | 是 | 0-23 | , - * / |
日 | 是 | 1-31 | , - * ? / L W |
月 | 是 | 1-12 或 JAN-DEC | , - * / |
周 | 是 | 1-7 或 SUN-SAT | , - * ? / L # |
年 | 否 | 1970-2099 | , - * / |
需要说明的是,Cron 表达式中,“周” 是从周日开始计算的。“周” 域上的 1
表示的是周日,7
表示周六。
每天晚上12点触发任务:
0 0 0 * * ?
每隔 1 分钟执行一次:
0 */1 * * * ?
每月 1 号凌晨 1 点执行一次:
0 0 1 1 * ?
每月最后一天 23 点执行一次:
0 0 23 L * ?
每周周六凌晨 3 点实行一次:
0 0 3 ? * L
在24分,30分执行一次:
0 24,30 * * * ?
是不是有点没看懂,没关系,我们可以使用Cron表达式生成器帮助我们生成Cron表达式
7|07.@Schedule实现定时任务
很多时候我们都需要为系统建立一个定时任务来帮我们做一些事情,SpringBoot 已经帮我们实现好了一个,我们只需要直接使用即可
一、引入依赖
二、开启注解
在 SpringBoot 中我们只需要在启动类上加上@EnableScheduling
便可以启动定时任务了。
三、创建scheduled task
启动项目便可以看到效果。
__EOF__

本文链接:https://www.cnblogs.com/wugongzi/p/14306143.html
关于博主:我是说故事的五公子,欢迎扫描左边二维码关注
版权声明:沪漂一员,互联网架构方向,如有问题探讨可以直接私信我,亦可下方留言。
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角【推荐】一下。您的鼓励是博主的最大动力!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 周边上新:园子的第一款马克杯温暖上架
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· 使用C#创建一个MCP客户端
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!