01.Quartz
Quartz
1.什么时候需要任务调度
- 信用卡账单日还款日通知
- 银行核心系统跑批
- 人行二代支付系统半个小时打一次包
2.任务调度的组成
- 触发调度的规则
- 调度后执行的行为:代码,任务,脚本
- 集中管理配置
- 并发执行,互不干扰
- 调度器,管理任务
- 集成Spring
3.任务调度工具
- Cron Tab
- Timer
- ScheduledThreadPoolExecutor
- Quartz
- SpringTask
- Elastic-Job
- XXL-JOB
4.定时任务的基础知识
- 小顶堆
- 时间轮算法
5.小顶堆
什么是堆
堆是—种特殊的树,只要满足下面两个条件,它就是一个堆:
- 堆是一颗完全二叉树
- 堆中某个节点的值总是不大于(或不小于)其父节点的值
- 最后一层靠左排列
什么是完全二叉树
在二叉树中,除了最后一层,每一层都是满的才可以称之为完全二叉树
数组与完全二叉树
完全二叉树最适合用数组做存储,因为它的节点都是紧凑的,且只有最后一层节点数不满
为什么下标0的位置不存在元素呢?
这是因为这样存储我们可以很方便地找到父节点:下标/2,比如,4的父节点即4/2=2,5的父节点即5/2=2。
小顶堆定义
堆也是一颗完全二叉树,但是它的元素必须满足每个节点的值都不大于(或不小于(小顶堆))其父节点的值。比如下面这个堆就是一个小顶堆:
小顶堆的存储实现
这种存储能够根据下标位置很快找到父节点
这时候我们要找8的父节点就拿8的位置下标5/2=2,也就是5这个节点的位置,这也是为了我们后面堆化
小顶堆插入元素
插入尾部,然后上浮
小顶堆删除元素
将尾部(最大的元素)元素放到堆顶,然后下沉
6.时间轮算法
为什么拥有了小顶堆还需要时间轮
小顶堆虽然也能实现定时调度,但是定时任务对于删除操作是比较频繁的,在小顶堆结构中需要频繁的下沉操作会很浪费性能
时间轮的分类
- while-true-sleep时间轮
- round型时间轮
- 分层时间轮
7.Timer
什么是Timer
是JDK提供的定时调度的一个类
使用Timer
public class Test {
public static void main(String[] args){
// new的时候任务就启动了
Timer t = new Timer();
for(int i=0;i<2;i++){
TimerTask task = new FooTimerTask("foo"+i);
// 任务提交
t.schedule(task,new Date(),2000);
}
}
}
class FooTimerTask extends TimerTask {
private String name;
public FooTimerTask(String name){
this.name=name;
}
public void run(){
try{
Syste.out.println("name="+name);
Thread.sleep(3000);
}catch(Exception e){
}
}
}
Timer的问题
- 单线程执行任务,任务可能互相阻塞
- 运行时异常会导致Timer线程终止
- 任务调度是基于绝对时间的,对系统时间敏感,无法做到类似于每个月的10天去做什么这种调度
8.ScheduledThreadPoolExecutor
相比于Timer的优点
- 使用多线程执行任务,不会相互阻塞
- 如果线程失活,会新建线程执行任务
- 也是用小顶堆实现的
Leader-Follower模式
假如说现在有一堆等待执行的任务(一般是存放在一个队列中排好序),而所有的工作线程中只会有一个是leader线程,其他的线程都是follower线程。只有leader线程能执行任务,而剩下的follower线程则不会执行任务,它们会处在休眠中的状态。当leader线程拿到任务后执行任务前,自己会变成follower线程,同时会选出一个新的leader线程,然后才去执行任务。如果此时有下一个任务,就是这个新的leader线程来执行了,并以此往复这个过程。当之前那个执行任务的线程执行完毕再回来时,会判断如果此时已经没任务了,又或者有任务但是有其他的线程作为leader线程,那么自己就休眠了;如果此时有任务但是没有leader线程,那么自己就会重新成为leader线程来执行任务
避免没必要的唤醒和阻塞的操作,这样会更加有效,且节省资源。
9.Quartz
Quartz介绍
Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,它可以与2EE与)2SE应用程序相结合也可以单独使用
Quartz是一个特性丰富的,开源的任务调度库,它几乎可以嵌入所有的Java程序,从很小的独立应用程序到大型商业系统。Quartz可以用来创建成百上千的简单的或者复杂的任务,这些任务可以用来执行任何程序可以做的事情。Quartz拥有很多企业级的特性,包括支持JTA事务和集群
Quartz运行环境
- Quartz可以运行嵌入在另一个独立式应用程序
- Quartz可以在应用程序服务器(或servlet容器)内被实例化,并且参与事务
- Quartz可以作为一个独立的程序运行(其自己的Java虚拟机内),可以通过RMI使用
- Quartz可以被实例化,作为独立的项目集群(负载平衡和故障转移功能),用于作业的执行
Quartz设计模式
- Builder设计模式
- 工厂模式
- 组件模式
- 链式编程
Quartz核心概念
任务(Job)
Job就是你想要实现的任务类,每一个ob必须实现org.quartz.job接口,且只需实现接口定义的execute()方法
触发器(Trigger)
Trigger为你执行任务的触发器,比如你想每天定时3点发送一份统计邮件,Trigger将会设置3点进行执行该任务。Trigger主要包含两种SimpleTrigger和CronTrigger两种
调度器(Scheduler)
Scheduler为任务的调度器,它会将任务job及触发器Trigger整合起来,负责基于Trigger设定的时间来执行Job
Quartz的体系结构
Quartz的几个常用API
- Scheduler用于与调度程序交互的主程序接口,Scheduler调度程序-任务执行计划表,只有安排进执行计划的任务Job(通过scheduler.scheduleJob方法安排进执行计划),当它预先定义的执行时间到了的时候(任务触发Trigger),该任务才会执行
- Job我们预先定义的希望在未来时间能被调度程序执行的任务类,我们可以自定义
- JobDetail使用JobDetail来定义定时任务的实例,JobDetail实例是通过JobBuilder类创建的
- JobDataMap可以包含不限量的(序列化)数据对象,在Job实例执行的时候,可以使用其中的数据。JobDataMap是Java Map接口的一个实现,额外增加了一些便于存取基本类型的数据的方法
- Trigger触发器,Trigger对象用来触发执行Job的。当调度一个Job时,我们实例一个触发器然后调整它的属性来满足Job执行的条件。表明任务在什么时候会执行。定义了一个已经被安排的任务将会在什么时候执行的时间条件,比如2秒就执行一次
- JobBuilder:用于声明一个任务实例,也可以定义该任务的详情比如任务名,组名等,这个声明的实例将会作为一个实际执行的任务
- TriggerBuilder:触发器创建器,用于创建触发器Trigger实例
- JobListener,TriggerListener,SchedulerListener监听器,用于对组件的监听
Quartz基本使用
依赖
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.2</version>
</dependency>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz-jobs</artifactId>
<version>2.3.2</version>
</dependency>
实现Job接口
/**
* @author 单国玉
* @date 2022/8/17 12:35
* @description
*/
public class HelloJob implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
System.out.println("当前开始时间:"+LocalDateTime.now().toString());
System.out.println("当前结束时间:"+LocalDateTime.now().toString());
}
}
测试运行
/**
* @author 单国玉
* @date 2022/8/17 12:34
* @description
*/
public class TestQuartz {
public static void main(String[] args) throws Exception{
// 1.调度器(Scheduler)
Scheduler defaultScheduler = StdSchedulerFactory.getDefaultScheduler();
// 2.任务实例(JobDetail)
JobDetail jobDetail = JobBuilder.newJob(HelloJob.class)
.withIdentity("job1", "group1") // 任务名称与任务组的名称
.build();
// 3.触发器(Trigger)
SimpleTrigger simpleTrigger = TriggerBuilder.newTrigger()
.withIdentity("trigger1", "group1") // 触发器名称与组名称
.startNow() // 马上启动触发器
.withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(5).repeatForever())
.build();
// 4.关联Job与Trigger
defaultScheduler.scheduleJob(jobDetail, simpleTrigger);
// 5.启动
defaultScheduler.start();
}
}
Job和JobDetail介绍
- Job: 工作任务调度的接口,任务类需要实现该接口。该接口中定义execute方法,类似JDK提供的TimerTask类的run方法。在里面编写任务执行的业务逻辑
- Job实例在Quartz中的生命周期: 每次调度器执行Job时,它在调用execute方法前会创建一个新的Job实例,当调用完成后,关联的Job对象实例会被释放,释放的实例会被垃圾回收机制回收
- JobDetail: JobDetail为Job实例提供了许多设置属性,以及JobDetaMap成员变量属性,它用来存储特定Job实例的状态信息,调度器需要借助JobDetail对象来添加Job实例
- JobDetail重要属性: name,group,jobClass,jobDataMap
JobExecutionContext介绍
- 当Scheduler调用一个Job,就会将JobExecutionContext传递给Job的execute方法
- Job能通过JobExecutionContext对象访问到Quartz运行时候的环境以及Job本身的明细数据
JobDataMap介绍
- 使用Map获取,在进行任务调度时候JobDataMap存储在JobExecutionContext中非常方便获取
- JobDataMap可以用来装载任何可序列化的数据对象,当Job实例对象被执行时这些参数对象会传递给它
- JobDataMap实现了JDK的Map接口,并且添加了非常方便的方法用来存取基本数据类型
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 25岁的心里话
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现