定时任务
timer类
public void schedule(TimerTask task, long delay, long period)
- task – 所要安排的任务
- delay – 执行任务前的延迟时间,单位是毫秒
- 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核心概念有三个
- JobDetail与业务类进行绑定,业务类可以通过继承QuartzJobBean或者实现job接口实现
- Trigger触发器 可以配置定时任务的时间,提供了cron表达式,方便简洁
- 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 jobDetail = JobBuilder.newJob(Myjob.class).withIdentity("job").build(); SimpleTrigger myTrigger = TriggerBuilder.newTrigger().withIdentity("myTrigger").startNow().withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(3).repeatForever()).build(); 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
| public class JobBuilder {
private JobKey key;
private Class<? extends Job> jobClass;
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; }
|
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); }
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"; }
} }
|
Triggers部分源码
1 2 3 4 5 6 7 8 9
| 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 * ?, 其中最后一位只能用?,而不能使用,如果使用表示不管星期几都会触发,实际上并不是这样。
- 0 0/5 * * * ? – 每5分钟就会触发一次
- 10 0/5 * * * ?– 每5分钟触发一次,分钟后10秒(比如上午10时10分触发,下次上午10:05:10等)
- 0 30 10-13 ?* WED,FRI – 在每个星期三和星期五的10:30,11:30,12:30和13:30创建触发器的表达式
- 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 jobDetail = JobBuilder.newJob(Myjob.class).withIdentity("job").build(); CronTrigger myTrigger = TriggerBuilder.newTrigger().withIdentity("myTrigger1").withSchedule(CronScheduleBuilder.cronSchedule("* * * * * ? ")).build();
Scheduler scheduler = new StdSchedulerFactory().getScheduler(); scheduler.start(); scheduler.scheduleJob(jobDetail,myTrigger);
|
springboot+qurartz
- 启动类添加@EnableScheduling注解
- 定时任务类添加@Component被容器扫描
- 定时任务的方法添加@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 获取源码