最近用到定时任务,这里总结一下java中常用的几种定时方法。
Java自带的java.util.Timer类,这个类允许你调度一个java.util.TimerTask任务。使用这种方式可以让你的程序按照某一个频度执行,但不能在指定时间运行。一般用的较少。 Spring和QuartZ都支持cron,功能都很强大,Spring的优点是稍微简单一点,QuartZ的优点是没有Spring也可使用;
一、Timer
这里我们看一下例子:
package test;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
/**
* 一个实现TimerTask的类(执行具体任务)
* @author cye
*
*/
public class TimerTest extends TimerTask {
private Timer timer;
public static void main(String[] args) {
TimerTest timerTest = new TimerTest();
timerTest.timer = new Timer();
// 立刻开始执行timerTest任务,只执行一次
// timerTest.timer.schedule(timerTest, new Date());
// 立刻开始执行timerTest任务,执行完本次任务后,隔2秒再执行一次
// timerTest.timer.schedule(timerTest,new Date(),2000);
// 一秒钟后开始执行timerTest任务,只执行一次
// timerTest.timer.schedule(timerTest,1000);
// 一秒钟后开始执行timerTest任务,执行完本次任务后,隔2秒再执行一次
// timerTest.timer.schedule(timerTest,1000,2000);
// 立刻开始执行timerTest任务,每隔2秒执行一次
timerTest.timer.scheduleAtFixedRate(timerTest, new Date(), 2000);
// 一秒钟后开始执行timerTest任务,每隔2秒执行一次
// timerTest.timer.scheduleAtFixedRate(timerTest,1000,2000);
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 结束任务执行,程序终止
timerTest.timer.cancel();
// 结束任务执行,程序并不终止,因为线程是JVM级别的
// timerTest.cancel();
}
@Override
public void run() {
System.out.println("Task is running!");
}
}
二、Quartz
Quartz 是个开源的作业调度框架,为在 Java 应用程序中进行作业调度提供了简单却强大的机制。Quartz 允许开发人员根据时间间隔(或天)来调度作业。它实现了作业和触发器的多对多关系,还能把多个作业与不同的触发器关联。整合了 Quartz 的应用程序可以重用来自不同事件的作业,还可以为一个事件组合多个作业。虽然可以通过属性文件(在属性文件中可以指定 JDBC 事务的数据源、全局作业和/或触发器侦听器、插件、线程池,以及更多)配置 Quartz,但它根本没有与应用程序服务器的上下文或引用集成在一起。结果就是作业不能访问 Web 服务器的内部函数;例如,在使用 WebSphere 应用服务器时,由 Quartz 调度的作业并不能影响服务器的动态缓存和数据源。
作业和触发器
Quartz 调度包的两个基本单元是作业和触发器。
调度器:调度器用于将与作业触发器关联,一个作业可关联多个触发器,这样每个触发器被可以触发的作业执行;一个触发器可用于控制多个作业,触发触发时,全部作业将获得调度。Quartz的调度器由Scheduler接口体现。
作业 是能够调度的可执行任务,触发器 提供了对作业的调度(有SimpleTrigger和CronTrigger两种类型)。虽然这两个实体很容易合在一起,但在 Quartz 中将它们分离开来是有原因的,而且也很有益处。通过把要执行的工作与它的调度分开,Quartz 允许在不丢失作业本身或作业的上下文的情况下,修改调度触发器。而且,任何单个的作业都可以有多个触发器与其关联。
- 作业: 首先创建一个实现了org.quartz.Job接口的类,并实现这个接口的唯一一个方法execute(JobExecutionContext arg0) throws JobExecutionException
package com.crystal.springmvc.timer;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
/**
* @author cye
*/
public class SimpleQuartzJob implements Job {
/**
* @param context:作业实例的运行时上下文,它提供了对调度器和触发器的访问,这两者协作来启动作业以及作业的 JobDetail 对象的执行。
* Quartz 通过把作业的状态放在 JobDetail 对象中并让 JobDetail 构造函数启动一个作业的实例,分离了作业的执行和作业周围的状态。
* JobDetail 对象储存作业的侦听器、群组、数据映射、描述以及作业的其他属性。
*/
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
System.out.println("In SimpleQuartzJob - executing its JOB at " + sdf.format(new Date()) + " by " + context.getTrigger());
}
}
2.Cron 触发器
CronTrigger 基于 cron 表达式,支持类似日历的重复间隔
package com.crystal.springmvc.timer;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.quartz.CronScheduleBuilder;
import org.quartz.CronTrigger;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SchedulerFactory;
import org.quartz.TriggerBuilder;
import org.quartz.impl.StdSchedulerFactory;
public class SimpleTriggerRunner {
public void task() throws SchedulerException {
//实例化一个 SchedulerFactory,获得此调度器
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = schedulerFactory.getScheduler();
// 初始化job任务
JobDetail jobDetail = JobBuilder.newJob(SimpleQuartzJob.class)
.withIdentity("jobDetail-s1", "jobDetailGroup-s1").build();
// 初始化触发器 CronTrigger
CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity("simpleTrigger", "triggerGroup-s1")
.startAt(new Date()).withSchedule(CronScheduleBuilder.cronSchedule("0/2 * * * * ?")).build();
Date ft = scheduler.scheduleJob(jobDetail, trigger); // 注册并进行调度
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
System.out.println(jobDetail.getKey() + " 已被安排执行于: " + sdf.format(ft)
+ ",并且以如下重复规则重复执行: " + trigger.getCronExpression());
scheduler.start(); // 开始执行,start()方法被调用后,计时器就开始工作,计时调度中允许放入N个Job
try { // 主线程等待一分钟
Thread.sleep(60L * 1000L);
} catch (Exception e) {
}
scheduler.shutdown(true); // 关闭定时调度,定时器不再工作
}
}
Cron 表达式包括以下 7 个字段:
格式: [秒] [分] [小时] [日] [月] [周] [年]
序号 | 说明 | 是否必填 | 允许填写的值 | 允许的通配符 |
---|---|---|---|---|
1 | 秒 | 是 | 0-59 | , - * / |
2 | 分 | 是 | 0-59 | , - * / |
3 | 小时 | 是 | 0-23 | , - * / |
4 | 日 | 是 | 1-31 | , - * ? / L W |
5 | 月 | 是 | 1-12 or JAN-DEC | , - * / |
6 | 周 | 是 | 1-7 or SUN-SAT | , - * ? / L # |
7 | 年 | 否 | empty 或 1970-2099 | , - * / |
通配符说明:
* 表示所有值. 例如:在分的字段上设置 “*”,表示每一分钟都会触发。
? 表示不指定值。使用的场景为不需要关心当前设置这个字段的值。例如:要在每月的10号触发一个操作,但不关心是周几,所以需要周位置的那个字段设置为”?” 具体设置为 0 0 0 10 * ?
- 表示区间。例如 在小时上设置 “10-12”,表示 10,11,12点都会触发。
* , 表示指定多个值,例如在周字段上设置 “MON,WED,FRI” 表示周一,周三和周五触发
/ 用于递增触发。如在秒上面设置”5/15” 表示从5秒开始,每增15秒触发(5,20,35,50)。在月字段上设置’1/3’所示每月1号开始,每隔三天触发一次。
L 表示最后的意思。在日字段设置上,表示当月的最后一天(依据当前月份,如果是二月还会依据是否是润年[leap]), 在周字段上表示星期六,相当于”7”或”SAT”。如果在”L”前加上数字,则表示该数据的最后一个。例如在周字段上设置”6L”这样的格式,则表示“本月最后一个星期五”
W 表示离指定日期的最近那个工作日(周一至周五). 例如在日字段上设置”15W”,表示离每月15号最近的那个工作日触发。如果15号正好是周六,则找最近的周五(14号)触发, 如果15号是周未,则找最近的下周一(16号)触发.如果15号正好在工作日(周一至周五),则就在该天触发。如果指定格式为 “1W”,它则表示每月1号往后最近的工作日触发。如果1号正是周六,则将在3号下周一触发。(注,”W”前只能设置具体的数字,不允许区间”-“).
注意: ‘L’和 ‘W’可以一组合使用。如果在日字段上设置”LW”,则表示在本月的最后一个工作日触发(一般指发工资 )
# 序号(表示每月的第几个周几),例如在周字段上设置”6#3”表示在每月的第三个周六.注意如果指定”#5”,正好第五周没有周六,则不会触发该配置(用在母亲节和父亲节再合适不过了)
注意:周字段的设置,若使用英文字母是不区分大小写的 MON 与mon相同.
Corn表达式在线验证:http://cron.qqe2.com/