前言:关于任务定时调度,Android基本用的要么Timer要么Handler配合Timer,而Java后端基本也是Timer;而其实,除了Timer,还有一个更强大的任务调度工具----Quartz。
Timer【概述】
Timer就不多说了
- 作用: 在一个子线程中执行定时或循环的任务。
- 包:
java.util.Timer
- 相关方法:
Timer.schedule(参数)
参数
如下:
举一个简单的例子:
问:现在有两个任务,即时任务A 【执行一次/ 1s】和耗时任务B【执行一次/ 500s】,我想先启动任务A,5s后启动任务B,并且10s后让两个任务都结束。
答:
Timer timer = new Timer();
timer.schedule(new TimerTask() {// 任务A
@Override
public void run() {
System.out.println("Timer正在调度 线程:" + Thread.currentThread().getName() + " 执行任务 A 中。。。");
}
}, 0, 1000)
timer.schedule(new TimerTask() {// 任务B
@Override
public void run() {
System.out.println("Timer正在调度 线程:" + Thread.currentThread().getName() + " 执行任务 B 中。。。");
try {
System.out.println("任务B很慢,它让Timer单例线程睡眠3s");
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, 5000, 500);
try {
System.out.println("主线程睡眠10s");
Thread.sleep(10000);
timer.cancel();
System.out.println("主线程关闭了Timer的所有任务");
} catch (InterruptedException e) {
e.printStackTrace();
}
Quartz【重点】
Quartz,一句话: 他就是Timer 的升级版
github地址: https://github.com/quartz-scheduler/quartz
一、最主要的接口和类
- 接口
-
Job
:任务。 -
JobDetail
: 任务详情(extends Serializable, Cloneable)。 -
Trigger
: 触发器(extends Serializable, Cloneable, Comparable<Trigger>)。 -
SchedulerFactory
:调度器工厂。 -
Scheduler
: 调度器。
-
- 类
-
JobBuilder
:任务详情构造器。 -
TriggerBuilder
:触发器构造器。 -
StdSchedulerFactory
:具体的调度器工厂。 -
SimpleTrigger
:Trigger实现类,和Timer实现功能差不多。 -
CronTrigger
:Trigger实现类,也是Quartz拉开Timer的地方。 -
StdSchedulerFactory
:标准的调度器工厂类
-
二、例子
上个代码可能更容易懂:
- 导入Quartz 包:
- Maven:
<dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> <version>2.3.0</version> </dependency>
- Gradle:
compile 'org.quartz-scheduler:quartz:2.3.0'
- 首先,创建一个
Job
实现类,名为JobImpl
:
public class JobImpl implements Job{
///获取key:value 方法二:通过设置与key同名同属性的成员变量,并设置getter和setter方法来实现值获取
// 在Quartz反射构建Job对象实例时,会自动判断和调用相应的setter方法,传入key同名的参数。
private String stringkeyA;
private Double doubleKeyB;
public String getStringkeyA() {
return stringkeyA;
}
public void setStringkeyA(String stringkeyA) {
this.stringkeyA = stringkeyA;
}
public Double getDoubleKeyB() {
return doubleKeyB;
}
public void setDoubleKeyB(Double doubleKeyB) {
this.doubleKeyB = doubleKeyB;
}
public void execute(JobExecutionContext context) throws JobExecutionException {
///do sth
///Job 可以获取 的 信息如下:
///获取 JobDetail 对象引用
JobDetail jobDetail = context.getJobDetail();
///获取job 的 标识名 和 所在组别
JobKey jobKey = jobDetail.getKey();
jobKey.getName();
jobKey.getGroup();
/// 获取 Trigger 对象引用
Trigger trigger = context.getTrigger();
///获取trigger 的 标识名 和 所在组别
TriggerKey triggerKey = trigger.getKey();
triggerKey.getName();
triggerKey.getGroup();
// 获取 JobDetail 中设定的key:value
JobDataMap jobMap = jobDetail.getJobDataMap();
// 获取 Trigger 中设定的key:value
JobDataMap triggerMap = trigger.getJobDataMap();
/// 合并并获取 JobDetail 和 Trigger 对象上设置的 key:value
JobDataMap map = context.getMergedJobDataMap();
///获取key:value 方法一:通过Map中获取
String stringKeyA = map.getString("stringKeyA");
String doubleKeyA = map.getString("doubleKeyA");
}
}
- 然后,根据这个
JobImpl
和一些任务参数配置,构造一个专门做JobImpl
这类任务的JobDetail
对象:
//创建一个JobDetail ,并让其与 JobImpl 绑定起来
JobDetail jobDetail = JobBuilder
.newJob(JobImpl.class)
.withIdentity("JobA", "group1")
.usingJobData("stringkeyA","任务DataA")
.usingJobData("doublekeyB",2.15D)
.build();
- 接着,创建一个Trigger对象,用于待会被调度器触发,Trigger用于设置任务的触发时间以及触发周期等。
//创建一个Trigger实例,并定义该job立即执行,每隔2s重复执行一次,直到永远
Trigger trigger = TriggerBuilder.newTrigger().withIdentity("triggerA", "group1")
.startNow()
.withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(2).repeatForever())
.usingJobData("stringkeyA","Trigger DataB")
.usingJobData("floatkeyC",3.1f)
.build();
- 最后,创建一个调度器,让其开始跑任务。
SchedulerFactory factory = new StdSchedulerFactory();///标准工厂
Scheduler scheduler = factory.getScheduler();///构建 调度器 实例
scheduler.scheduleJob(jobDetail,cronTrigger);// 让调度器 绑定 任务详情 和 触发器
scheduler.start();//执行任务
//........
scheduler.standby();//我想暂时挂起调度器,不让其执行任务
//........
scheduler.start();/// 重启调度器
//........
scheduler.shutdown(true);//终止调度器(true:等所有job都跑完,才终止;false:不等job,直接终止)
三、关于Trigger实现类
- SimpleTrigger
//以上的Trigger 实际上默认就是一个SimpleTrigger
Date startTime = new Date(System.currentTimeMillis());
Date endTime = new Date(System.currentTimeMillis()+3000);
SimpleTrigger simpleTrigger = (SimpleTrigger) TriggerBuilder.newTrigger()
.withIdentity("mySimpleTrigger","G1")
.startAt(startTime)
.endAt(endTime)
.withSchedule(SimpleScheduleBuilder
.simpleSchedule()
.withRepeatCount(3)//重复次数:0,正整数,或无穷
.withIntervalInSeconds(2))// 执行周期: 0,长整数
.build();
- CropTrigger【重点】
- 基于Cron表达式:
- 由7个子表达式组成 的 字符串,描述了时间表的详细信息
- 格式:
[秒] [分] [小时] [日] [月] [周] [年]
-
Cron表达式规则:
- 符号意义:[
,
或者] [-
范围] [*
每] [/
每隔] [?
不关心(与*
意义差不多)] [L
最后的一X] [W
距离X号最近的一个工作] [#
第]。 - Cron表达式例子:
-
0 15 10 ? * *
:每天10点15分触发。 -
0 0/5 14 * * ?
:每天下午2点到2点59分,每隔5分钟执行一次。 -
0 15 10 ? * MON-FRI
:周一到周五每天上午10点15分触发。 -
0 15 10 ? * 7#3
:每月第三周的星期六 触发。 -
0 15 10 ? * 6L 2016-2017
:从2016年到2017年每个月最后一周的周五触发。
-
- 符号意义:[
- CropTrigger例子:
///CronTrigger 基于日历的调度(功能强大, 可定义每周周几 执行一次任务 之类的) /** * Cron表达式: * 由7个子表达式组成 的 字符串,描述了时间表的详细信息 * 格式: [秒] [分] [小] [时] [日] [月] [周] [年] */
CronTrigger cronTrigger = TriggerBuilder
.newTrigger()
.withIdentity("cronTrigger", "G1")
.withSchedule(
CronScheduleBuilder.cronSchedule("* * * * * ? *")///每一秒执行一次
)
.build();
### 四、注意点
1. **`ExecutionContext`在构建任务调度器的时候,就会被引入到`Job`中。**
2. **`ExecutionContext`本身拥有`JobDetail` 和`Trigger`的引用,这样,就可以给`Job`的执行过程中,随时获取到`JobDetail`和`Trigger`的信息。**
3. **`JobDataMap`是`ExecutionContext`携带的重要参数,它携带了`JobDetail`和`Trigger`在构造时设置的一些基本数据类型的参数。**
4. **`Job`获取`JobDataMap`中的参数的方式**【上述例子中有注释】
* 法一:直接从Map对象中获取的方式
```
JobDataMap jMap = jobExecutionContext.getJobDetail().getJobDataMap();
JobDataMap tMap = jobExecutionContext.getTrigger().getJobDataMap();
JobDataMap mergeMap = jobExecutionContext.getMergedJobDataMap();
```
> 注意:上述`mergeMap`会将`Trigger`中设定的属性,覆盖掉`JobDetail`中设定的同名属性。
* 法二:自定义Job子类中事先定义几个成员属性并实现其getter和setter方法,在构建Job实例时,会自动调用Job子类的setter方法,将key同名的value赋值到Job实例中。
5. **关于quartz.properties文件**
* 它是调度器参数的配置文件,包括:
1. 调度器属性
2. 线程池属性
3. 存储调度信息
4. 插件属性配置
* `StdSchedulerFactory`使用`Java.util.Properties`来创建和初始化Quartz调度器,在创建`Scheduler`对象时,就是根据`quartz.properties`文件中配置信息。
* Quartz 默认情况下,会读取project下的`quartz.properties`文件,找不到文件,则会去查找引入的jar包下的`quartz.properties`文件,此文件拥有Quartz的一些相关配置信息。
* 用法:
1. 一般情况下,将`Jar包目录/org.quartz/`下的`quartz.properties`文件copy 到你的项目根目录下。
2. 然后,可以自行配置各个参数。【具体配置可以查看[Quartz官网](http://www.quartz-scheduler.org/)】
# 总结一波
---
实际上, Timer 和 Quartz 有一个共同的理念,就是:将想要在某些时刻执行的任务交给工作线程,并让该线程通过阻塞等机制,到了某一时刻执行。
只不过,Timer内部是一个单例线程和一个数组类型的队列结构,多个任务可以入队等待,但同一时刻只有一个任务会被执行,并没有并发这一点,每个任务在执行一次后,如果有循环,那么则会重新入队。
而Quartz ,相对于Timer的优势,总体来说,两点:
1. 更强的定时调度能力:Quartz的CronTrigger,拥有Timer无法实现的日历定时调度功能。
2. 任务并发能力:Quartz内部通过线程池管理多个线程,能够让任务并发执行,从而避免了使用Timer时由于前一个耗时任务导致队列中后一个任务错过执行时间的情况。