定时任务

timer类

public void schedule(TimerTask task, long delay, long period)

  1. task – 所要安排的任务
  2. delay – 执行任务前的延迟时间,单位是毫秒
  3. period – 执行各后续任务之间的时间间隔,单位是毫秒
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class TimerDemo {
public static void main(String[] args) {
final Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("timer1的定时任务");
}
}, 0, 1000);
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("timer2的定时任务");
}
},0,1000);
}
}
控制台输出
timer1的定时任务
timer2的定时任务
timer1的定时任务
timer1的定时任务
timer2的定时任务

timer存在问题点:

通过点击TimerTask类源码发现,TimerTask实现了Runnable接口,覆写了run方法,timer实际是利用多线程进行处理定时任务,如果此刻TimerTask任务出现异常,而Timer类并不会处理,将终止timer线程,这种情况下,Timer也不会再重新恢复线程的执行了;它错误的认为整个Timer都被取消了。此时,已经被安排但尚未执行的TimerTask永远不会再执行了,新的任务也不能被调度了。我们将timer2的任务中加入异常,进行测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class TimerDemo {
public static void main(String[] args) {
final Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("timer1的定时任务");
}
}, 0, 1000);
timer.schedule(new TimerTask() {
@Override
public void run() {
//模拟某一定时任务出现异常情况
int a = 1/0;
System.out.println("timer2的定时任务");
}
},0,1000);
}
}
控制台输出
Exception in thread "Timer-0" java.lang.ArithmeticException: / by zero
at com.chenghao.timer.TimerDemo$2.run(TimerDemo.java:22)
at java.util.TimerThread.mainLoop(Timer.java:555)
at java.util.TimerThread.run(Timer.java:505)
timer1的定时任务

可以看出利用Timer类有着致命的缺陷,因此我们引入成熟的quartz框架


Quartz

quartz核心概念有三个

  1. JobDetail与业务类进行绑定,业务类可以通过继承QuartzJobBean或者实现job接口实现
  2. Trigger触发器 可以配置定时任务的时间,提供了cron表达式,方便简洁
  3. Scheduler 调度器,对业务类和触发器进行管理,启动

先进行简单实现,具体实现后面有详细说明

1 引入quartz的依赖

1
2
3
4
5
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.0</version>
</dependency>

2 编写作业类,及具体业务类代码,如给用户发送生日短信

1
2
3
4
5
6
7
//创建作业类,公司具体业务
public class job implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
System.out.println("开启quartz任务");
}
}

3 编写JobDetail,Trigger触发器,Scheduler 调度器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class QuartzDemo {
public static void main(String[] args) throws SchedulerException {
//创建jobDetail,与job业务类进行绑定
JobDetail jobDetail = JobBuilder.newJob(Myjob.class).withIdentity("job").build();
//创建触发器Trigger startNow()立即执行 withSchedule() 加入执行时间
SimpleTrigger myTrigger = TriggerBuilder.newTrigger().withIdentity("myTrigger").startNow().withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(3).repeatForever()).build();
//创建schedule实例
Scheduler scheduler = new StdSchedulerFactory().getScheduler();
scheduler.start();
scheduler.scheduleJob(jobDetail,myTrigger);
}
}
控制台输出
开启quartz任务
开启quartz任务
开启quartz任务
开启quartz任务

至此,我们利用quartz进行简单定时任务已经完成了,我们开始进一步讲解JobDetail,Trigger触发器,Scheduler 调度器核心三要素,可以发现三者的实例都是通过Builder模式进行创建的,通过调用builder的set中方法,来设置参数,这样就保证了在生成实例的时候直接就绑定了数据,我们具体看看源码里面实现


JobDetail部分源码

1
2
3
4
5
6
7
8
9
10
11
12
//JobBuilder中一些字段
public class JobBuilder {

// 将Job和Trigger注册到Scheduler时,可以为它们设置key,配置其身份属性。
private JobKey key;

// 具体业务类当Job的一个trigger被触发时,execute()方法由调度程序的一个工作线程调用
private Class<? extends Job> jobClass;

// JobExecutionContext对象中保存着该job运行时的一些信息
private JobDataMap jobDataMap = new JobDataMap();
}

JobDetail jobDetail = JobBuilder.newJob(Myjob.class).withIdentity(“job”).build();具体实现

1
2
3
4
5
6
7
8
9
10
11
12
13
第一步 newJob
JobBuilder.newJob(Myjob.class)--》

public static JobBuilder newJob(Class<? extends Job> jobClass) {
JobBuilder b = new JobBuilder();
b.ofType(jobClass);
return b;
}
public JobBuilder ofType(Class<? extends Job> jobClazz) {
this.jobClass = jobClazz;
return this;
}
//可以发现是很简单的,设置jobClass属性值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
第二步 withIdentity
JobBuilder.newJob(Myjob.class).withIdentity("job") -》

public JobBuilder withIdentity(String name) {
this.key = new JobKey(name, (String)null);//调用了两个参数的构造器
return this;
}

public JobKey(String name, String group) {
super(name, group); // jobKey 继承Key
}


public Key(String name, String group) {
if(name == null) {
throw new IllegalArgumentException("Name cannot be null.");
} else {
this.name = name;
if(group != null) {
this.group = group;
} else {
this.group = "DEFAULT";
}

}
}
//给JobKey属性赋值完成,如果不传group,默认名为DEFAULT

Triggers部分源码

1
2
3
4
5
6
7
8
9
//TriggerBuilder中一些字段
public class TriggerBuilder<T extends Trigger> {
private TriggerKey key;
private Date startTime = new Date();
private Date endTime;
private JobKey jobKey;
private JobDataMap jobDataMap = new JobDataMap();
private ScheduleBuilder<?> scheduleBuilder = null;
}

TriggerBuilder.newTrigger().withIdentity(“myTrigger”)和上面处理过程几部一样,TriggerKey属性和JobBuilder类中JobKey属性都继承与Key,重点讲解Trigger的两种触发器,一种指定时间执行,一种按指定频率执行

1. SimpleScheduleBuilder

上述代码中创建Trigger方法中使用的SimpleScheduleBuilder,满足的调度需求是:在具体的时间点执行一次,或者在具体的时间点执行,并且以指定的间隔重复执行若干次具体看下部分源码实现

1
2
3
4
5
6
7
public class SimpleScheduleBuilder extends ScheduleBuilder<SimpleTrigger> {
// 触发器的时间间隔,单位毫秒
private long interval = 0L;

// 重复次数
private int repeatCount = 0;
}

SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(3).repeatForever() 及表示3秒触发一次, repeatForever() 方法及给repeatCount=0表示不重复启用,返回的TriggerBuilder类可以用来设置开始时间和结束时间,接下来介绍的触发器CronTrigger更加有用

2. CronTrigger

通常比Simple Trigger更有用,如果您需要基于日历的概念而不是按照SimpleTrigger的精确指定间隔进行重新启动的作业启动计划。使用CronTrigger, Cron Expressions是由七个子表达式组成的字符串,用于描述日程表的各个细节.

** 表示匹配该域的任意值,假如在Minutes域使用* *,即表示每分钟都会触发事件

? 用在DayofMonth和DayofWeek会相互影响,指定一个另一个必须为? 例如想在每月的20日触发调度,不管20日到底是星期几,则只能使用如下写法: 13 13 15 20 * ?, 其中最后一位只能用?,而不能使用,如果使用表示不管星期几都会触发,实际上并不是这样。

  1. 0 0/5 * * * ? – 每5分钟就会触发一次
  2. 10 0/5 * * * ?– 每5分钟触发一次,分钟后10秒(比如上午10时10分触发,下次上午10:05:10等)
  3. 0 30 10-13 ?* WED,FRI – 在每个星期三和星期五的10:30,11:30,12:30和13:30创建触发器的表达式
  4. 0 0/30 8-9 5,20 * ? -创建触发器的表达式,每个月5日和20日上午8点至9点之间每半小时触发一次。请注 意,触发器将不会在上午10点开始,仅在8:00,8:30,9:00和9:30
1
2
3
4
5
6
7
//创建jobDetail,与job业务类进行绑定
JobDetail jobDetail = JobBuilder.newJob(Myjob.class).withIdentity("job").build();
CronTrigger myTrigger = TriggerBuilder.newTrigger().withIdentity("myTrigger1").withSchedule(CronScheduleBuilder.cronSchedule("* * * * * ? ")).build();
//创建schedule实例
Scheduler scheduler = new StdSchedulerFactory().getScheduler();
scheduler.start();
scheduler.scheduleJob(jobDetail,myTrigger);

springboot+qurartz

  1. 启动类添加@EnableScheduling注解
  2. 定时任务类添加@Component被容器扫描
  3. 定时任务的方法添加@Scheduled(cron = “0 41 23 11 8 ? “)表达式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//启动类
@SpringBootApplication
@EnableScheduling
public class SchedulerApplication {
public static void main(String[] args) {
SpringApplication.run(SchedulerApplication.class, args);
}
}
//测试类
@Component
public class springbootQuartzDemo {
@Scheduled(cron = "0 41 23 11 8 ? ")
private void test() {
System.out.println("springbootQuartzDemo定时任务开启");
}
}
控制台输出
springbootQuartzDemo定时任务开启

click 获取源码