前言

任务调度是指基于给定时间点,给定时间间隔或者给定执行次数自动执行任务。项目中有很独特的应用的场景,比如每天凌晨同步数据,定时操作业务等等。

  • Timer
  • scheduler
  • quartz

1 Timer

java.util.Timer,可以实现一些简单的定时任务,使用 Timer 实现任务调度的核心类是 Timer 和 TimerTask。其中 Timer 负责设定 TimerTask 的起始与间隔执行时间。使用者只需要创建一个 TimerTask 的继承类,实现自己的 run 方法,然后将其丢给 Timer 去执行即可。 Timer 将接收到的任务丢到自己的 TaskList 中,TaskList 按照 Task 的最初执行时间进行排序。TimerThread 在创建 Timer 时会启动成为一个守护线程。这个线程会轮询所有任务,找到一个最近要执行的任务,然后休眠,当到达最近要执行任务的开始时间点,TimerThread 被唤醒并执行该任务。之后 TimerThread 更新最近一个要执行的任务,继续休眠如下。 Timer 的优点在于简单易用,但由于所有任务都是由同一个线程来调度,因此所有任务都是串行执行的,同一时间只能有一个任务在执行,前一个任务的延迟或异常都将会影响到之后的任务:

public class TimerTest extends TimerTask {
    private String jobName = "";

    public TimerTest(String jobName) {
        super();
        this.jobName = jobName;
    }

    @Override
    public void run() {
        System.out.println("execute " + jobName);
    }

    public static void main(String[] args) {
        Timer timer = new Timer();
        long delay1 = 1 * 1000;
        long period1 = 1000;
        // 从现在开始 1 秒钟之后,每隔 1 秒钟执行一次 job1
        timer.schedule(new TimerTest("job1"), delay1, period1);
        long delay2 = 2 * 1000;
        long period2 = 2000;
        // 从现在开始 2 秒钟之后,每隔 2 秒钟执行一次 job2
        timer.schedule(new TimerTest("job2"), delay2, period2);

    }
}

scheduler 单机服务常用的调度任务策略。

2.xml配置

				<beans xmlns:task="http://www.springframework.org/schema/task"xsi:schemaLocation="http://www.springframework.org/schema/task   http://www.springframework.org/schema/task/spring-task-3.1.xsd">
				
				<task:executor id="executor" pool-size="15" />  
				<task:scheduler id="scheduler" pool-size="30" />  
				<task:annotation-driven executor="executor" scheduler="scheduler" />

2.corn 表达式时间设定,可参考corn表达式——用于设置定时任务

				@Scheduled(cron = "*/5 * * * * *")
				public void test1() throws InterruptedException {
				log.info("test1, 5秒执行一次,每次执行sleep 8s");
				}

3.注意事项:

  • 3.1 同一定时任务,第二次触发时间到了,第一次还没有执行完成时会执行吗?不会,会等前一次执行完成才执行下一次
  • 3.2 不同的定时任务,相互之间是否有影响?取决于可用的定时任务线程数,如果线程数足够则不会影响;如果可用定时任务线程数少于要执行定时任务数量,未能获取到线程的自然要等到有空闲线程时才能执行。

quartz 集群发布web服务器任务策略

可以解决如果是集群发布web服务器,就会导致每个服务器都跑一遍这个定时器,会出现定时器被多次执行方法。quartz可以解决这个问题,quartz是用javaweb服务器和数据库等配合完成的一个定时器选择服务器执行的机制实现的,这样每次到执行的时候只选择一个合适的web服务器去执行定时任务。

1.配置maven依赖

				<!-- 定时任务 -->
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz</artifactId>
        </dependency>

2.Scheduler 调度程序配置

<description>Quartz的定时集群任务配置</description>
    <bean name="quartzScheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
        <!--必须,QuartzScheduler 延时启动,应用启动后 QuartzScheduler 再启动 -->
        <property name="startupDelay" value="60"/>
        <!-- 普通触发器 :触发器列表 -->
        <property name="triggers">
            <list>                
                <ref local="syncFacTimerTrigger"/>
            </list>
        </property>
        <property name="quartzProperties">
            <props>
                <!-- 主要是这个参数 -->
                <prop key="org.quartz.scheduler.skipUpdateCheck">true</prop>
                <prop key="org.terracotta.quartz.skipUpdateCheck">true</prop>
            </props>
        </property>
    </bean>

3.Trigger 触发器的配置

<!--Cron表达式触发器-->
    <bean id="syncFacTimerTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
        <property name="jobDetail">
            <ref bean="QuartzSyncFacJob"/> <!-- 触发器触发的 执行定时任务的bean -->
        </property>
        <property name="cronExpression">
             <value>0 0/30 * * * ?</value>  <!--30分钟一次-->
            <!--  <value>0 0/1 * * * ?</value> --> <!-- 1分钟一次 -->
            <!--<value>0 0 2 * * ?</value> <!–每天2点 –>-->
            <!--<value>0 0/1 * * * ?</value> 1分钟一次 -->
        </property>
    </bean>

4.Job 调度任务配置

<bean id="QuartzSyncFacJob" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
        <!-- durability 表示任务完成之后是否依然保留到数据库,默认false -->
        <property name="durability" value="true"/>
        <!-- 目标类 /wmuitp/src/test/SpringQuartz.java -->
        <property name="jobClass" value="com.hec.dup.web.schedule.QuartzSyncFacJob"/><!--任务类-->
        <property name="applicationContextJobDataKey" value="applicationContext"/>
        <property name="jobDataAsMap"><!-- 非常重要,用来向JobDetail传参 -->
            <map>
                <entry key="dupMgrOrgReviseDoService" value-ref="dupMgrOrgReviseDoService"/><!--注入的servise名称-->
            </map>
        </property>
    </bean>

5 Job类,用于处理相关业务逻辑

public class QuartzSyncFacJob extends QuartzJobBean {
    private static Logger logger = LoggerFactory.getLogger(QuartzJobBean.class);

    @Override
    public void executeInternal(JobExecutionContext context) throws JobExecutionException {
        syncData(context);
    }

    public void syncData(JobExecutionContext context) {
        //通过bean注入到jobDataAsMap中,再获取
        IDupMgrOrgReviseDoService dupMgrOrgReviseDoService = (IDupMgrOrgReviseDoService) context.getJobDetail().getJobDataMap().get("dupMgrOrgReviseDoService");
				//处理相关逻辑
        dupMgrOrgReviseDoService.syncSaveOrUpMdcFactory(); 
    }

}

以上是日常用到任务调度,当然spring提供了多种方式的调度注入方式,除了bean,还有各种@注入方式,有兴趣的可以自己研究。