java.util.Timer vs Quartz
从调度的灵活度比较
Timer | Quartz |
---|---|
从指定时间执行一次 | Timer能做的都能做 |
从firstTime时刻开始,每隔period毫秒执行一次 | |
从现在起过delay毫秒执行一次 | |
从现在起过delay毫秒以后,每隔period毫秒执行一次 | Timer不能做的也能做 |
从调度数据的存储方式比较
Timer | Quartz |
---|---|
内存 | 内存&数据库 |
What is Quartz?
Quartz是由Java实现的开源任务调度框架。作为一个优秀的任务调度框架,必然具有卓越的特点:
- 强大的调度功能
不但能够定时执行任务、周期执行任务,还能够在某一时间点执行任务。支持丰富多样的调度方法,满足各种常规及殊需求。 - 灵活的应用方式
支持任务和调度的多种组合方式,支持调度数据的多种存储方式。 - 灵活的部署方式
提供分布式和集群部署能力。 - Spring默认的调度框架
不但在Spring中可以使用,在Jboss、JIRA、Jarkata等中都可以使用。
Hello, Quartz!
寥寥几行代码,展示Quartz的魅力。
public class HelloJob implements Job {
public void execute(JobExecutionContext context) throws JobExecutionException {
Date date = new Date();
String dateStr = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date);
JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();
String para = jobDataMap.getString("para");
System.out.println(para + " - " + dateStr);
}
public static void main(String[] args) {
try {
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
String jobName = "HelloJob";
String jobGroup = Scheduler.DEFAULT_GROUP;
JobDetail jobDetail = JobBuilder.newJob(HelloJob.class).withIdentity(jobName, jobGroup).build();
JobDataMap jobDataMap = jobDetail.getJobDataMap();
jobDataMap.put("para", "hello");//job执行的参数
//创建触发器:每5秒执行一次,共执行3+1次
SimpleScheduleBuilder builder = SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(5).withRepeatCount(3);
Trigger trigger = TriggerBuilder.newTrigger().withIdentity(jobName, jobGroup).startNow().withSchedule(builder).build();
scheduler.scheduleJob(jobDetail, trigger);
scheduler.start();//启动scheduler
} catch (SchedulerException e) {
e.printStackTrace();
}
}
}
运行结果:
hello - 2016-11-10 15:20:26
hello - 2016-11-10 15:20:31
hello - 2016-11-10 15:20:36
hello - 2016-11-10 15:20:41
核心元素
在上面的例子中,可以看出Quartz几个核心元素。
1.Scheduler
Scheduler是任务调度容器,其中可以存放若干对JobDetail和Trigger。当Scheduler启动后,JobDetail就会根据Trigger按部就班的自动执行。
在Quartz中,Scheduler由Scheduler工厂创建:StdSchedulerFactory或DirectSchedulerFactory。目前,StdSchedulerFactory使用较多。
在Quartz中,Scheduler有4种:JBoss4RMIRemoteMBeanScheduler、RemoteMBeanScheduler、RemoteScheduler、StdScheduler。其中,以StdScheduler最为常用。
创建Scheduler的方法如下:
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
2.Trigger
Trigger制定了一个时间规则,告诉作业什么时候可以工作。Quartz提供了8种Trigger:CalendarIntervalTrigger、CoreTrigger、CronTrigger、DailyTimeIntervalTrigger、LocalityTrigger、MutableTrigger、OperableTrigger、SimpleTrigger。其中,CronTrigger和SimpleTrigger最为常用,且CronTrigger最为灵活。
创建SimpleTrigger方法如下:
SimpleScheduleBuilder builder = SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(5).withRepeatCount(3);
Trigger trigger = TriggerBuilder.newTrigger().withIdentity(jobName, jobGroup).startNow().withSchedule(builder).build();
创建CronTrigger方法如下:
String cron = "0 55 14 * * ?";
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cron);
// 按照cron表达式构建一个新的trigger
CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(jobName, jobGroup).withSchedule(scheduleBuilder).build();
3.Job
Job是Trigger触发后,在触发时间点到达时,执行的具体内容。在Quartz中,主要有两种类型的Job:无状态(stateless)的Job和有状态(stateful)的Job。两者的区别是:对于有状态的Job,不能被并行执行,只能在上一次触发执行结束后,才能触发执行。
无论哪种Job,只需实现execute接口即可:
public class HelloJob **implements Job** {
public void **execute**(JobExecutionContext context) throws JobExecutionException {
……
}
}
4.JobDetail
Job是不能直接与Trigger结合的,只有通过JobDetail才能将Job和Trigger结合起来,并为Job设置属性、传递参数。
JobDetail jobDetail = JobBuilder.newJob(HelloJob.class).withIdentity(jobName, jobGroup).build();
JobDataMap jobDataMap = jobDetail.getJobDataMap();
jobDataMap.put("para", "hello");//job执行的参数
……
scheduler.scheduleJob(jobDetail, trigger);
Cron表达式
Quartz有两大触发器,除了在上面示例中使用的SimpleTrigger外,还有一个就是无所不能的CronTrigger。CronTrigger能够提供复杂的触发器表达式的支持,是基于Unix Cron守护进程,是一个调度程序,支持简单而强大的触发器语法。
而只要掌握了Cron表达式,就掌握了CronTrigger的精髓。Cron表达式包含7部分,其中:6个是必要组件,1个是可选组件。这7部分分别为:
秒 分 小时 日期 月 星期 年
下面详细介绍一下Cron中的每一部分:
位置 | 含义 | 特殊字符 |
---|---|---|
1 | 秒(0-59) | , - * / |
2 | 分(0-59) | , - * / |
3 | 小时(0-24) | , - * / |
4 | 日期(1-31) | , - * / ? L W C |
5 | 月(JAN-DEC或1-12) | , - * / |
6 | 星期(SUN-SAT或1-7) | , - * / ? L C # |
7 | 年(可选,1970-2099) | , - * / |
每一部分又允许一些特殊字符,这些特殊字符的含义如下表:
特殊字符 | 含义 |
---|---|
* | 通配符,任意值 |
? | 无特定值。通常和其他指定的值一起使用,表示必须显示该值但不能检查 |
- | 范围。例如,小时部分10-12表示10:00,11:00, 12:00 |
, | 列分隔符。可以指定一系列的值。例如,在星期域中指定MON、TUE和WED |
/ | 增量。例如,分钟域中0/1表示从0开始,每次增加1min |
L | 表示Last。在日期和星期域中表示有所不同。在日期域中,表示这个月的最后一天,而在星期域中,永远是7(星期六)。当希望使用星期中某一天时,L字符非常有用。例如,星期域中6L表示每一个月的最后一个星期五 |
W | 在本月内离当天最近的工作日触发,所谓的最近工作日,即当天到工作日的前后最短距离,如果当天即为工作日,则距离是0;所谓本月内指的是不能跨月取到最近工作日,即使前/后月份的最后一天/第一天确实满足最近工作日。例如,LW表示本月的最后一个工作日触发,W强烈依赖月份。 |
# | 表示该月的第几个星期,例如,1#2表示每一个月的第一个星期一。 |
C | 日历值。日期值是根据一个给定的日历计算出来的。在日期域中给定一个20C将在20日(日历包括20日)或20日后日历中包含的第一天(不包括20日)激活触发器。例如在一个星期域中使用6C表示日历中星期五(日历包括星期五)或者第一天(日历不包括星期五)。 |
每一部分又允许一些特殊字符,这些特殊字符的含义如下表:
理论太多,还是举一些例子:
时间表达式 | 含义 |
---|---|
30 * * * * ? | 每半分钟触发任务 |
30 10 * * * ? | 每小时的10分30秒触发任务 |
30 10 1 * * ? | 每天1点10分30秒触发任务 |
30 10 1 20 * ? | 每月20号1点10分30秒触发任务 |
30 10 1 20 10 ? * | 每年10月20号1点10分30秒触发任务 |
30 10 1 20 10 ? 2011 | 2011年10月20号1点10分30秒触发任务 |
30 10 1 ? 10 * 2011 | 2011年10月每天1点10分30秒触发任务 |
30 10 1 ? 10 SUN 2011 | 2011年10月每周日1点10分30秒触发任务 |
15,30,45 * * * * ? | 每15秒,30秒,45秒时触发任务 |
15-45 * * * * ? | 15到45秒内,每秒都触发任务 |
15/5 * * * * ? | 每分钟的每15秒开始触发,每隔5秒触发一次 |
15-30/5 * * * * ? | 每分钟的15秒到30秒之间开始触发,每隔5秒触发一次 |
0 0/3 * * * ? | 每小时的第0分0秒开始,每三分钟触发一次 |
0 15 10 ? * MON-FRI | 星期一到星期五的10点15分0秒触发任务 |
0 15 10 L * ? | 每个月最后一天的10点15分0秒触发任务 |
0 15 10 LW * ? | 每个月最后一个工作日的10点15分0秒触发任务 |
0 15 10 ? * 5L | 每个月最后一个星期四的10点15分0秒触发任务 |
0 15 10 ? * 5#3 | 每个月第三周的星期四的10点15分0秒触发任务 |
说的再多,也不如一个实例看的明白。既然在开始举了一个SimpleTrigger的实例,接下来就为CronTrigger来一个实例。
public class CronJob implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
JobDataMap dataMap = context.getJobDetail().getJobDataMap();
String date = dataMap.getString("date");
System.out.println("HelloJob - " + date);
}
public static void main(String[] args) {
try {
SchedulerFactory sf = new StdSchedulerFactory();
Scheduler scheduler = sf.getScheduler();
String jobName = "HelloJob";
String jobGroup = Scheduler.DEFAULT_GROUP;
// 构建job信息
JobDetail jobDetail = JobBuilder.newJob(CronJob.class).withIdentity(jobName, jobGroup).build();
String dateStr = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
jobDetail.getJobDataMap().put("date", dateStr);
// 每天下午2点55触发
String cron = "0 55 14 * * ?";
CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cron);
// 按cron表达式构建一个trigger
CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(jobName, jobGroup).withSchedule(scheduleBuilder).build();
scheduler.scheduleJob(jobDetail, trigger);
scheduler.start();
} catch (SchedulerException e) {
e.printStackTrace();
}
}
}