java.util.Timer vs Quartz

从调度的灵活度比较

Timer

Quartz

从指定时间执行一次

Timer能做的都能做

从firstTime时刻开始,每隔period毫秒执行一次

 

从现在起过delay毫秒执行一次

 

从现在起过delay毫秒以后,每隔period毫秒执行一次

Timer不能做的也能做

从调度数据的存储方式比较

Timer

Quartz

内存

内存&数据库

What is Quartz?

Quartz是由Java实现的开源任务调度框架。作为一个优秀的任务调度框架,必然具有卓越的特点:

  1. 强大的调度功能
    不但能够定时执行任务、周期执行任务,还能够在某一时间点执行任务。支持丰富多样的调度方法,满足各种常规及殊需求。
  2. 灵活的应用方式
    支持任务和调度的多种组合方式,支持调度数据的多种存储方式。
  3. 灵活的部署方式
    提供分布式和集群部署能力。
  4. 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
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();
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();
        }
     }
}