单机定时任务的四种基本实现方式

引言

在实际项目开发中,定时任务调度是经常会出现的一类需求。
定时任务的场景可以说非常广泛,例如:

  • 购买某些视频网站的会员后,每天给会员送成长值,每月给会员送电影券
  • 在保证最终一致性的场景中,利用定时任务调度进行一些数据核对的工作
  • 通过邮件定时发送报表和工作提醒
  • 需要定时清理数据的任务

本文将介绍单机定时任务的基本实现方式,可以覆盖到定时任务调度最基本的使用场景:包括:

  • Timer与TimerTask
  • ScheduledExecutorService
  • Spring Task
  • Quartz

正文

方式一:JDK原生定时工具:Timer

简介

JDK提供的Timer类,允许调度一个TimerTask任务。Timer位于java.util包下,其内部包含且仅包含一个后台线程(TimeThread)对多个业务任务(TimeTask)进行定时定频率的调度。

schedule的四种用法和scheduleAtFixedRate的两种用法:

public void schedule(TimerTask task, long delay);
public void schedule(TimerTask task, Date time);
public void schedule(TimerTask task, long delay, long period);
public void schedule(TimerTask task, Date firstTime, long period);
public void scheduleAtFiexRate(TimerTask task, long delay, long period);
public void scheduleAtFiexRate(TimerTask task, Date firstTime, long period);

参数说明:

  • task:所要执行的任务,需要实现TimeTask的run()方法
  • time/firstTime:首次执行任务的时间
  • period:周期性执行Task的时间间隔,单位是毫秒
  • delay:执行task任务前的延时时间,单位是毫秒

很显然,通过上述的描述,我们可以实现:

  • 延迟多久后执行一次任务
  • 指定时间执行一次任务
  • 延迟一段时间,并周期性执行任务
  • 指定时间,并周期性执行任务

代码示例

编写MyTimerTask任务类,用以表示具体需要执行的任务:

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimerTask;
import java.util.concurrent.atomic.AtomicInteger;

public class MyTimerTask extends TimerTask {
    /**
     * The action to be performed by this timer task.
     */
    private static final SimpleDateFormat FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    private AtomicInteger count = new AtomicInteger(0);

    public void run() {
        System.out.println("执行时间:" + FORMAT.format(this.scheduledExecutionTime()));
        count.incrementAndGet();
        if (count.get() == 10) {
            this.cancel();
            System.out.println("达到预定执行次数,取消执行计划");
        }
    }
}

编写TimerDemo类,为需要调度的任务设置调度运行参数:

package com.netease.scaffold.task;

import java.util.Date;
import java.util.Timer;

public class TimerDemo {
    public static void main(String[] args) {
        // 创建定时器
        Timer timer = new Timer();

        // 添加调度任务
        // schedule(TimerTask task, Date time); 特定时间 time 执行
        // timer.schedule(new MyTimerTask(), new Date(System.currentTimeMillis() + 1000));

        // schedule(TimerTask task, long delay); //延迟 delay毫秒 执行 task
        // timer.schedule(new MyTimerTask(), 1000);

        // schedule(TimerTask task, long delay, long period) 延迟 delay毫秒 执行并每隔 period毫秒 执行一次
        // timer.schedule(new MyTimerTask(), 1000, 5000);

        // schedule(TimerTask task, Date time, long period); 特定时间 time 执行并每隔 period毫秒 执行一次
        timer.schedule(new MyTimerTask(), new Date(System.currentTimeMillis() + 1000), 1000);

    }
}

测试结果

执行时间:2018-12-25 11:58:00
执行时间:2018-12-25 11:58:01
执行时间:2018-12-25 11:58:02
执行时间:2018-12-25 11:58:03
执行时间:2018-12-25 11:58:04
执行时间:2018-12-25 11:58:05
执行时间:2018-12-25 11:58:06
执行时间:2018-12-25 11:58:07
执行时间:2018-12-25 11:58:08
执行时间:2018-12-25 11:58:09
达到预定执行次数,取消执行计划

不难发现,输出是在当前执行时刻延迟1秒后开始执行的,后面每隔1秒执行一次。达到计划的执行次数之后,取消执行计划。

点评

思考1:如果time/firstTime指定的时间,在当前时间之前,会发生什么呢?

在时间等于或者超过time/firstTime的时候,会执行task!也就是说,如果time/firstTime指定的时间在当前时间之前,就会立即得到执行。

思考2:schedule和scheduleAtFixedRate有什么区别?

scheduleAtFixedRate:每次执行时间为上一次任务开始起向后推一个period间隔,也就是说下次执行时间相对于上一次任务开始的时间点,因此执行时间不会延后,但是存在任务并发执行的问题。
schedule:每次执行时间为上一次任务结束后推一个period间隔,也就是说下次执行时间相对于上一次任务结束的时间点,因此执行时间会不断延后。

思考3:如果执行task发生异常,是否会影响其他task的定时调度?

如果TimeTask抛出RuntimeException,那么Timer会停止所有任务的运行!

思考4:Timer的一些缺陷?
前面已经提及到Timer背后是一个单线程,因此Timer存在管理并发任务的缺陷:所有任务都是由同一个线程来调度,所有任务都是串行执行,意味着同一时间只能有一个任务得到执行,而前一个任务的延迟或者异常会影响到之后的任务。
其次,Timer的一些调度方式还算比较简单,无法适应实际项目中任务定时调度的复杂度。

这种只适合一些最基础的定时任务,作为玩具使用,在实际的项目开发中一般很少用到,了解即可。

方式二:JDK对定时任务调度的线程池支持:ScheduledExecutorService

由于Timer存在的问题,JDK5之后便提供了基于线程池的定时任务调度ScheduledExecutorService。它的设计理念是每一个被调度的任务都会被线程池中的一个线程去执行,因此任务可以并发执行,而且相互之间不受影响。

编写代码ScheduleExecutorServiceDemo

import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class ScheduleExecutorServiceDemo implements Runnable {
    @Override
    public void run() {
        System.out.println("执行:" + new Date());
    }

    public static void main(String[] args) {
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);
        scheduledExecutorService.scheduleAtFixedRate(new ScheduleExecutorServiceDemo(), 1000, 2000, TimeUnit.MILLISECONDS);
    }
}

测试结果

执行:Sun Mar 29 17:09:36 CST 2020
执行:Sun Mar 29 17:09:38 CST 2020
执行:Sun Mar 29 17:09:40 CST 2020
执行:Sun Mar 29 17:09:42 CST 2020
...

方式三:Spring Task

Spring也提供了对于每台机器都执行的定时任务的支持,熟悉Spring的同学都知道Spring一般都是同时支持XML配置和注解配置的方式的,下面我们将分别对这两种方式分别介绍。

XML配置方式

添加配置

<!--1. 基于配置的Spring Task-->
    <bean id="springTask" class="com.netease.scaffold.task.SpringTask"/>
    <!--注册调度任务-->
    <task:scheduled-tasks>
        <!--延迟1秒 执行任务-->
        <!--<task:scheduled ref="springTask" method="show1" fixed-delay="1000" />-->

        <!--固定速度3秒 执行任务-->
        <task:scheduled ref="springTask" method="show1" fixed-rate="3000"/>

        <!--
            使用cron表达式 指定触发时间
            spring task 只支持6位的cron表达式 秒 分 时 日 月 星期
        -->
        <!--cron表达式,每秒 执行任务-->
        <task:scheduled ref="springTask" method="show2" cron="*/1 * * * * ?"/>
    </task:scheduled-tasks>
    <!--执行器配置-->
    <task:executor id="threadPoolTaskExecutor" pool-size="10" keep-alive="5"/>
    <!--调度器配置-->
    <task:scheduler id="threadPoolTaskScheduler" pool-size="10"/>

编写测试任务类SpringTask

package com.netease.scaffold.task;

import java.text.SimpleDateFormat;
import java.util.Date;

public class SpringTask {

    private static final SimpleDateFormat FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    public void show1() {
        System.out.println("show1:" + FORMAT.format(new Date()));
    }

    public void show2() {
        System.out.println("show2:" + FORMAT.format(new Date()));
    }
}

测试结果

show2:2018-12-05 16:01:03
show2:2018-12-05 16:01:04
show1:2018-12-05 16:01:04
show2:2018-12-05 16:01:05
show2:2018-12-05 16:01:06
show2:2018-12-05 16:01:07
show1:2018-12-05 16:01:07
show2:2018-12-05 16:01:08
show2:2018-12-05 16:01:09
show2:2018-12-05 16:01:10
show1:2018-12-05 16:01:10
show2:2018-12-05 16:01:11
show2:2018-12-05 16:01:12
show2:2018-12-05 16:01:13
show1:2018-12-05 16:01:13
show2:2018-12-05 16:01:14
show2:2018-12-05 16:01:15
show2:2018-12-05 16:01:16
show1:2018-12-05 16:01:16
show2:2018-12-05 16:01:17

注解方式

定时任务启用注解

首先,需要在应用入口类上加上@EnableScheduling注解,表示启用注解扫描方式的定时任务。

测试类任务类SpringAnnoTask

package com.netease.scaffold.task;

import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * spring执行任务的类
 */
@Component
public class SpringAnnoTask {

    private static final SimpleDateFormat FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    @Scheduled(cron = "1-10 * *  * * ? ")//每分钟的1-10秒每秒执行一次
    public void show1() {
        System.out.println("show1:" + FORMAT.format(new Date()));
    }

    @Scheduled(cron = "0/10 * *  * * ? ")//每10秒执行一次
    public void show2() {
        System.out.println("show2:" + FORMAT.format(new Date()));
    }

        @Scheduled(fixedRate = 2000)//每两秒执行一次时间
    public void show3() {
        System.out.println("show3:" + FORMAT.format(new Date()));
    }

    @Scheduled(fixedDelay = 4000)//每次任务执行完之后的4s后继续执行
    public void show4() {
        System.out.println("show4:" + FORMAT.format(new Date()));
    }
}

测试结果

show2:2018-12-05 16:23:54
show1:2018-12-05 16:23:54
show3:2018-12-05 16:23:54
show2:2018-12-05 16:23:55
show2:2018-12-05 16:23:56
show3:2018-12-05 16:23:56
show4:2018-12-05 16:23:56
show2:2018-12-05 16:23:57
show1:2018-12-05 16:23:57
show2:2018-12-05 16:23:58
show3:2018-12-05 16:23:58
show2:2018-12-05 16:23:59
show2:2018-12-05 16:24:00
show2:2018-12-05 16:24:00
show1:2018-12-05 16:24:00
show3:2018-12-05 16:24:00
show4:2018-12-05 16:24:00
show2:2018-12-05 16:24:01
show1:2018-12-05 16:24:01
show2:2018-12-05 16:24:02
show1:2018-12-05 16:24:02

点评

spring task的特点:

  • 默认单线程同步执行
  • 一个任务执行完上一次之后,才会执行下一次调度
  • 多任务之间按顺序执行,一个任务执行完成之后才会执行另一个任务
  • 多任务并行执行需要设置线程池
  • 全程可以通过注解配置

方式四:Quartz框架

Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,是完全由java开发的一个开源的任务日程管理系统,“任务进度管理器”就是一个在预先确定(被纳入日程)的时间到达时,负责执行(或者通知)其他软件组件的系统。虽然ScheduledExecutorService对Timer进行了线程池的改进,但是依然无法满足复杂的定时任务调度场景。因此OpenSymphony提供了强大的开源任务调度框架:Quartz。Quartz是纯Java实现,而且作为Spring的默认调度框架,由于Quartz的强大的调度功能、灵活的使用方式、还具有分布式集群能力,可以说Quartz出马,可以搞定一切定时任务调度!

Quartz体系结构.png

特点

  • 强大的调度功能,例如支持丰富多样的调度方法,可以满足各种常规及特殊需求;
  • 灵活的应用方式,例如支持任务和调度的多种组合方式,支持调度数据的多种存储方式;
  • 分布式和集群能力,Terracotta 收购后在原来功能基础上作了进一步提升。
  • 另外,作为 Spring 默认的调度框架,Quartz 很容易与 Spring 集成实现灵活可配置的调度功能。

核心元素 :

  • Quartz有3个核心概念:调度器(Scheduler)、任务(Job&JobDetail)、触发器(Trigger)。(一个任务可以被多个触发器触发,一个触发器只能触发一个任务)
  • Scheduler: 任务调度器,是实际执行任务调度的控制器。在spring中通过SchedulerFactoryBean封装起来。
  • Trigger :触发器,用于定义任务调度的时间规则,有SimpleTrigger,CronTrigger,DateIntervalTrigger和NthIncludedDayTrigger,其中CronTrigger用的比较多,本文主要介绍这种方式。CronTrigger在spring中封装在CronTriggerFactoryBean中。
  • Calendar:它是一些日历特定时间点的集合。一个trigger可以包含多个Calendar,以便排除或包含某些时间点。
  • **Job **:任务,是一个接口,只有一个方法void execute(JobExecutionContext context),开发者实现该接口定义运行任务,JobExecutionContext类提供了调度上下文的各种信息。Job运行时的信息保存在JobDataMap实例中。实现Job接口的任务,默认是无状态的,若要将Job设置成有状态的,在quartz中是给实现的Job添加@DisallowConcurrentExecution注解(以前是实现StatefulJob接口,现在已被Deprecated),在与spring结合中可以在spring配置文件的job detail中配置concurrent参数。
  • JobDetail :任务信息,用来描述Job实现类及其它相关的静态信息,如Job名字、关联监听器等信息。在spring中有JobDetailFactoryBean和 MethodInvokingJobDetailFactoryBean两种实现,如果任务调度只需要执行某个类的某个方法,就可以通过MethodInvokingJobDetailFactoryBean来调用。
  • 注意当Scheduler调度Job时,实际上会通过反射newInstance一个新的Job实例(待调度完毕后销毁掉),同时会把JobExecutionContext传递给Job的execute方法,Job实例通过JobExecutionContext访问到Quartz运行时的环境以及Job本身的明细数据。
  • JobDataMap可以装载任何可以序列化的数据,存取很方便。需要注意的是JobDetail和Trigger都可以各自关联上JobDataMap。JobDataMap除了可以通过上述代码获取外,还可以在YourJob实现类中,添加相应setter方法获取。
  • 实际上,Quartz在进行调度器初始化的时候,会加载quartz.properties文件进行一些属性的设置,比如Quartz后台线程池的属性(threadCount)、作业存储设置等。它会先从工程中找,如果找不到那么就是用quartz.jar中的默认的quartz.properties文件。
  • Quartz存在监听器的概念,比如任务执行前后、任务的添加等,可以方便实现任务的监控。

Trigger触发器 :

Trigger用来告诉Quartz调度程序什么时候执行,常用的触发器有2种:SimpleTrigger(类似于Timer)、CronTrigger(类似于Linux的Crontab)。

  • SimpleTrigger :在一个指定时间段内执行一次作业任务或是在指定时间间隔内执行多次作业任务;
  • CronTrigger :基于日历的作业调度器,而不是像SimpleTrigger那样精确指定间隔时间,比SimpleTrigger更常用。

引入依赖包

<dependency>
  <groupId>commons-collections</groupId>
  <artifactId>commons-collections</artifactId>
   <version>3.2.1</version>
</dependency>

 <dependency>
   <groupId>org.springframework</groupId>
  <artifactId>spring-context-support</artifactId>
  <version>4.3.11.RELEASE</version>
</dependency>

 <dependency>
  <groupId>org.quartz-scheduler</groupId>
  <artifactId>quartz</artifactId>
  <version>2.2.1</version>
 </dependency>

添加配置信息

在Spring的配置文件ApplicationContext.xml添加如下的信息:

<!--引入其他配置参数.如果参数文件不止一个,由于spring容器只会维护一个PropertyPlaceholderConfigurer的bean实例,当spring发现容器中有一个该实例后,就会忽略其余的,所以,该标签只能配置一个,多余的spring会自动忽略。需要加上这个设置ignore-unresolvable="true",否则会报错-->
<context:property-placeholder location="classpath:scaffold.properties" ignore-unresolvable="true"/>

<!--===============================Quartz定时任务配置=========================================-->
    <!--调度任务1:实现job接口,可获取job上下文信息-->
    <bean id="simpleTriggerJob" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
        <property name="jobClass" value="com.netease.scaffold.task.SimpleTriggerJob"/>
        <property name="jobDataAsMap">
            <map>
                <entry key="triggerMessage1" value="Job Message In JobDetail"/> <!--设置JobDetail中的值-->
            </map>
        </property>
    </bean>
    <!--调度触发器-->
    <!--触发器1:SimpleTrigger:每隔多少分钟小时执行,简单重复执行的场景下可用-->
    <bean id="simpleTriggerBean" class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean">
        <property name="jobDetail" ref="simpleTriggerJob"/> <!--触发的job引用-->
        <property name="startDelay" value="1000"/> <!--设置延迟1秒后运行-->
        <property name="repeatInterval" value="1000"/> <!--设置每1秒触发一次-->
        <property name="jobDataAsMap">
            <map>
                <entry key="triggerMessage2" value="Job Message From Trigger"/> <!--设置Trigger中的值-->
            </map>
        </property>
    </bean>

    <!--调度任务2:自定义的bean,无任何限制-->
    <bean id="cronTriggerJob" class="com.netease.scaffold.task.CronTriggerJob">
        <property name="taskName" value="${scaffold.app.taskName}"/>
    </bean>
    <!--通过MethodInvokingJobDetailFactoryBean,任务类可以不实现job接口,通过targetMethod指定调用方法-->
    <!--定义目标bean和bean中的方法-->
    <bean id="springQtzJobMethod" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
        <property name="targetObject" ref="cronTriggerJob"/>
        <!--定义需要定时执行的方法名称-->
        <property name="targetMethod" value="execute"/>
    </bean>
    <!--触发器2:CronTrigger:日历相关的重复时间间隔,如每天凌晨,每周星期一运行的话,通过Cron表达式便可定义出复杂的调度方案。-->
    <bean id="cronTriggerBean" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
        <property name="jobDetail" ref="springQtzJobMethod"/>
        <!-- cron表达式 -->
        <property name="cronExpression" value="*/1 * * * * ?"/>
    </bean>

    <!-- 定时任务调度工厂 -->
    <bean id="task-schedulerFactory" lazy-init="false" autowire="no"
          class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
        <property name="triggers">
            <list>
                <ref bean="simpleTriggerBean" />
                <ref bean="cronTriggerBean"/>
            </list>
        </property>
    </bean>

定时任务

  1. 编写测试定时任务测试代码SimpleTriggerJob:
package com.netease.scaffold.task;


import java.util.Map;

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

public class SimpleTriggerJob implements Job {

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {

        Map properties = context.getMergedJobDataMap();

        System.out.println("Hello World!");
        System.out.println("Previous Fire Time: " + context.getPreviousFireTime());//上次触发任务的时间
        System.out.println("Current Fire Time: " + context.getFireTime());//当前触发时间
        System.out.println("Next Fire Time: " + context.getNextFireTime());//下次触发时间

        System.out.println(properties.get("triggerMessage1"));
        System.out.println(properties.get("triggerMessage2"));
        System.out.println();
    }

}
  1. 编写测试定时任务测试代码CronTriggerJob:
package com.netease.scaffold.task;

import java.util.Date;

public class CronTriggerJob {
    private static Integer counter = 0;

    private static String taskName;

    public static String getTaskName() {
        return taskName;
    }

    public static void setTaskName(String taskName) {
        CronTriggerJob.taskName = taskName;
    }

    protected void execute() {
        long ms = System.currentTimeMillis();
        System.out.println("taskName:" + taskName);
        System.out.println("\t\t" + new Date(ms));
        System.out.println("(" + counter++ + ")");
    }
}

测试结果

测试输出内容每秒执行一次输出,跟预期结果一致。

(17)
2018-12-05 13:14:41,000 [DEBUG] org.quartz.core.JobRunShell - Calling execute on job DEFAULT.springQtzJobMethod
2018-12-05 13:14:41,000 [DEBUG] org.quartz.core.QuartzSchedulerThread - batch acquisition of 1 triggers
taskName:myTaskName
        Wed Dec 05 13:14:41 CST 2018
(18)
2018-12-05 13:14:42,000 [DEBUG] org.quartz.core.QuartzSchedulerThread - batch acquisition of 1 triggers
2018-12-05 13:14:42,000 [DEBUG] org.quartz.core.JobRunShell - Calling execute on job DEFAULT.springQtzJobMethod
taskName:myTaskName
        Wed Dec 05 13:14:42 CST 2018
(19)
2018-12-05 13:14:42,033 [DEBUG] org.quartz.core.JobRunShell - Calling execute on job DEFAULT.simpleTriggerJob
Hello World!
2018-12-05 13:14:42,033 [DEBUG] org.quartz.core.QuartzSchedulerThread - batch acquisition of 1 triggers
Previous Fire Time: Wed Dec 05 13:14:39 CST 2018
Current Fire Time: Wed Dec 05 13:14:42 CST 2018
Next Fire Time: Wed Dec 05 13:14:45 CST 2018
Job Message In JobDetail
Job Message From Trigger

使用原生Quartz

编写QuartzDemo

import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;

public class QuartzDemo {

    public static void main(String[] args) throws SchedulerException {
        // 定义执行任务
        JobDetail jobDetail = JobBuilder.newJob(SimpleTriggerJob.class)
                .withIdentity("myjob", "myGroup")
                .usingJobData("triggerMessage1", "louxj424")
                .usingJobData("triggerMessage2", "zhangsan")
                .build();

        // 定义简单执行的触发器:每两秒执行一次,直到永远
//      Trigger trigger = TriggerBuilder.newTrigger()
//              .withIdentity("triggerName", "triggerGroup")
//              .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(2).repeatForever())
//              .startNow()
//              .build();

        // 定义cron表示的触发器:每两秒执行一次,直到永远
        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("triggerName", "triggerGroup")
                .withSchedule(CronScheduleBuilder.cronSchedule("0/2 * * * * ? *"))
                .build();

        // 定义调度器
        SchedulerFactory schedulerFactory = new StdSchedulerFactory();
        Scheduler scheduler = schedulerFactory.getScheduler();
        scheduler.scheduleJob(jobDetail, trigger);
        scheduler.start();
    }
}

测试

Hello World!
Previous Fire Time: null
Current Fire Time: Tue Dec 25 12:02:12 CST 2018
Next Fire Time: Tue Dec 25 12:02:14 CST 2018
louxj424
zhangsan

Hello World!
Previous Fire Time: Tue Dec 25 12:02:12 CST 2018
Current Fire Time: Tue Dec 25 12:02:14 CST 2018
Next Fire Time: Tue Dec 25 12:02:16 CST 2018
louxj424
zhangsan

Hello World!
Previous Fire Time: Tue Dec 25 12:02:14 CST 2018
Current Fire Time: Tue Dec 25 12:02:16 CST 2018
Next Fire Time: Tue Dec 25 12:02:18 CST 2018
louxj424
zhangsan

cron表达式

Cron表达式是一个字符串,字符串以5或6个空格隔开,分为6或7个域,每一个域代表一个含义,Cron有如下两种语法格式:

​ (1)Seconds Minutes Hours DayofMonth Month DayofWeek Year

​ (2)Seconds Minutes Hours DayofMonth Month DayofWeek

特殊符号说明

特殊字符 含义
* 表示所有值。例如在分的字段上设置“*”,表示每一分钟都会触发。
? 表示不指定值。使用场景为不需要关心当前设置的这个字段的值。例如,要在每个月的10号出发一个操作,但是不关心是周几,所以需要在周位置设置为“?”,具体设置为“0 0 0 10 * ? *”
- 表示区间。例如,在小时位置设置10-12,表示10,11,12都会触发。
, 表示指定多个值,例如周字段上设置“MON,WEB,FRI”表示周一、周三和周五会触发。
/ 用于递增触发。如秒上面设置“5/15”,表示从5秒开始,每增15秒就触发一次,可以计算出来触发的时间依次为(5,20,35,50)。在月上设置“1/3”表示从每月1号开始,每隔三天触发一次。
L 表示最后的意思。在字段上设置,表示当月最后一天(依据当前月份),如果是二月还会根据是否是闰年。在周字段上表示星期六,相当于“7”或者“SAT”。如果在L前面加上数字,则表示该数据的最后一个。例如,在周字段上设置6L,表示本月的最后一个星期五。
W 表示离指定日期最近的那个工作日(周一至周五)。例如在日字段上设置“15W”,表示离每月15号最近的那个工作日触发。如果15号正好是星期六,则最近的是周五14号触发。如果15号是周末,则找最近的下周一16号触发。如果15号正好是在工作日(周一到周五),则就在当天触发。如果指定格式为“1W”,它表示每月1号往后最近的工作日触发。如果1号正好是星期六,则将在下周一也就是3号触发。(注:“W”前只能设置具体的数字,不允许区间)
# 序号,表示每月的第几个周几。例如在周字段上设置“6#3”表示在每月的第三个周五。

字段值说明

字段 是否必填 允许值 允许的特殊字符
0-59 , - * /
0-59 , - * /
小时 0-23 , - * /
1-31 , - * ? / L C
1-12或者JAN-DEC , - * /
1-7或者SUN-SAT . - * ? / L #
empty,1970-2099 , - * /

常用表达式举例

序号 表达式 说明
1 0 15 10 * * ? * 每天10点15分触发
2 0 15 10 * * ? 2017 2017年每天10点15分触发
3 0 * 14 * * ? 每天下午的 2点到2点59分每分触发
4 0 0/5 14 * * ? 每天下午的 2点到2点59分(整点开始,每隔5分触发)
5 0 0/5 14,18 * * ? 每天下午的 2点到2点59分、18点到18点59分(整点开始,每隔5分触发)
6 0 0-5 14 * * ? 每天14点到14点5分内每分种触发一次
7 0 15 10 ? * 6L 每月最后一周的星期五的10点15分触发
8 0 15 10 ? * 6#3 每月第三个星期五的10点15分触发

其他工具

我们可以通过一些Cron在线工具非常方便的生成。

点评

Spring Quartz 特点:【按照配置文件时间表达式:准时准点(不延时的时候)】 ---> 配置到spring application.xml上

    1. 默认多线程异步执行
    1. 一个任务在上一次调度未完成执行,下一次调度时间到时,会另起一个线程开始新的调度。在业务繁忙时,一个任务或许会有多个线程在执行,导致数据处理异常。
    1. 单任务同步:配置属性,可以使一个任务的一次调度在未完成时,而不会开启下一次调度
    1. 多个任务同时运行,任务之间没有直接的影响,多任务执行的快慢取决于CPU的性能
    1. 一个类对应不一样的job方法并可以定义为不一样的job task任务 [看配置文件内容]

推荐使用CronTrigger的触发器的方式,因为该方式无需继承任何父类或者实现任何的接口,对原有功能代码的侵入性比较小,而且可以实现指定时间执行和指定时间间隔执行,使用上几乎没有任何的限制,因此往往在工程代码中,见到的更多的是这种方式实现的定时调度任务。

参考资料

  1. Spring配置Quartz定时任务
  2. Spring+Quartz整合(2)之SimpleTrigger
  3. # java定时任务实现的几种方式(Timer、Spring Task、Quartz)
  4. Java定时任务调度详解
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,911评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,014评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 142,129评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,283评论 1 264
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,159评论 4 357
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,161评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,565评论 3 382
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,251评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,531评论 1 292
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,619评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,383评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,255评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,624评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,916评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,199评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,553评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,756评论 2 335

推荐阅读更多精彩内容