首页 > 技术文章 > Java定时任务

boomoom 2018-02-02 19:55 原文

主要有以下三种实现方式:

  • JDK自带:JDK 自带的 Timer 以及 JDK1.5+ 新增的 ScheduledExecutorService;

  • Quartz:简单却强大的 JAVA 作业调度框架;

  • Spring3.0以后自带的task :可以将它看成一个轻量级的Quartz,而且使用起来比Quartz简单许多;

JDK 自带的定时器实现

Timer

这个类允许你调度一个java.util.TimerTask任务。主要有以下几个方法:

  • schedule(TimerTask task, long delay) 延迟 delay 毫秒执行

  • schedule(TimerTask task, Date time) 特定时间执行

  • schedule(TimerTask task, long delay, long period) 延迟 delay 执行并每隔 period 执行一次。用固定延迟调度。使用本方法时,在任务执行中的每一个延迟会传播到后续的任务的执行。

  • scheduleAtFixedRate(TimerTask task, long delay, long period) 延迟 delay 执行并固定速率 period 执行一次。用固定比率调度。使用本方法时,所有后续执行根据初始执行的时间进行调度,从而希望减小延迟。

 

ScheduledExecutorService

 

该定时任务接口,主要有以下几个方法

  • ScheduledFuture<?> schedule(Runnable command,long delay, TimeUnit unit);

  • <V> ScheduledFuture<V> schedule(Callable<V> callable,long delay, TimeUnit unit);

  • ScheduledFuture<?> scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnitunit);

  • ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,long initialDelay,long delay,TimeUnitunit);

该接口的默认实现为 ScheduledThreadPoolExecutor 类,这个类继承了 ThreadPoolExecutor 类。线程池的使用使其比Timer更稳定。spring Task内部也是依靠它实现的。

Timer的缺陷

1、Timer在执行定时任务时只会创建一个线程,所以如果存在多个任务,且任务时间过长,超过了两个任务的间隔时间,后面的任务执行时间就被推迟。

package cn.boomoom.service;

import java.util.Timer;
import java.util.TimerTask;

public class TimerTest {

    private static long start;

    public static void main(String[] args) {

        TimerTask task1 = new TimerTask() {
            @Override
            public void run() {
                System.out.println("task1 invoked ! " + (System.currentTimeMillis() - start));
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };

        TimerTask task2 = new TimerTask() {
            @Override
            public void run() {
                System.out.println("task2 invoked ! " + (System.currentTimeMillis() - start));
            }
        };

        Timer timer = new Timer();
        start = System.currentTimeMillis();
        timer.schedule(task1, 1000);
        timer.schedule(task2, 3000);
    }
}
TimerTest

定义了两个任务,预计是第一个任务1s后执行,第二个任务3s后执行,但是看运行结果:

task1 invoked ! 1000
task2 invoked ! 4001

task2实际上是4后才执行,正因为Timer内部是一个线程,而任务1所需的时间超过了两个任务间的间隔导致。

package cn.boomoom.service;

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

public class ScheduledExecutorServiceTest {
    private static long start;

    public static void main(String[] args) {

        // 使用工厂方法初始化一个ScheduledThreadPool
        ScheduledExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(2);

        TimerTask task1 = new TimerTask() {
            @Override
            public void run() {
                try {
                    System.out.println("task1 invoked ! " + (System.currentTimeMillis() - start));
                    Thread.sleep(3000);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        };

        TimerTask task2 = new TimerTask() {
            @Override
            public void run() {
                System.out.println("task2 invoked ! " + (System.currentTimeMillis() - start));
            }
        };
        start = System.currentTimeMillis();
        newScheduledThreadPool.schedule(task1, 1000, TimeUnit.MILLISECONDS);
        newScheduledThreadPool.schedule(task2, 3000, TimeUnit.MILLISECONDS);
    }
}
ScheduledExecutorServiceTest
task1 invoked ! 1002
task2 invoked ! 3004

符合我们的预期结果。因为ScheduledThreadPool内部是个线程池,所以可以支持多个任务并发执行。

 

2、Timer当任务抛出异常时的缺陷。如果TimerTask抛出RuntimeException,Timer会停止所有任务的运行。

package cn.boomoom.service;

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

public class TimerTest01 {

    public static void main(String[] args) {

        final TimerTask task1 = new TimerTask() {
            @Override
            public void run() {
                throw new RuntimeException();
            }
        };

        final TimerTask task2 = new TimerTask() {
            @Override
            public void run() {
                System.out.println("task2 invoked!");
            }
        };

        Timer timer = new Timer();
        timer.schedule(task1, 100);
        timer.scheduleAtFixedRate(task2, new Date(), 1000);
    }
}
TimerTest01
task2 invoked!
Exception in thread "Timer-0" java.lang.RuntimeException
    at cn.itcast.bos.service.TimerTest01$1.run(TimerTest01.java:14)
    at java.util.TimerThread.mainLoop(Timer.java:555)
    at java.util.TimerThread.run(Timer.java:505)

由于任务1的一次,任务2也停止运行了。

package cn.boomoom.service;

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

public class ScheduledExecutorServiceTest01 {

    public static void main(String[] args) {

        final TimerTask task1 = new TimerTask() {
            @Override
            public void run() { throw new RuntimeException();}
        };

        final TimerTask task2 = new TimerTask() {
            @Override
            public void run() { System.out.println("task2 invoked!"); }
        };

        ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);
        pool.schedule(task1, 100, TimeUnit.MILLISECONDS);
        pool.scheduleAtFixedRate(task2, 0, 1000, TimeUnit.MILLISECONDS);
    }
}
ScheduledExecutorServiceTest01

task2 持续输出。ScheduledExecutorService 保证了,task1出现异常时,不影响task2的运行。

 

3、Timer执行周期任务时依赖系统时间,如果当前系统时间发生变化会出现一些执行上的变化,ScheduledExecutorService 基于时间的延迟,不会由于系统时间的改变发生执行变化。

 

Quartz 任务调度实现

quartz 的 java demo

http://www.quartz-scheduler.org/ 入门案例: http://www.quartz-scheduler.org/documentation/quartz-2.1.x/quick-start.html

1、导入maven坐标

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>cn.boomoom.maven</groupId>
    <artifactId>quartz_helloworld</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>quartz_helloworld</name>

    <dependencies>
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz</artifactId>
            <version>2.2.1</version>
        </dependency>
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz-jobs</artifactId>
            <version>2.2.1</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.12</version>
        </dependency>
    </dependencies>
</project>
pom

2、quartz demo

package cn.boomoom.service;

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

import java.util.HashMap;
import java.util.Map;

public class QuartzTest {
    public static void main(String[] args) throws SchedulerException {
        SchedulerFactory sf = new StdSchedulerFactory();
        Scheduler scheduler = sf.getScheduler();

        Map<JobDetail, Trigger> jobTriggerMap = jobAndTrigger();
        for (Map.Entry<JobDetail,Trigger> entry : jobTriggerMap.entrySet()) {
            scheduler.scheduleJob(entry.getKey(), entry.getValue());
        }

        scheduler.start();
    }

    private static Map<JobDetail, Trigger> jobAndTrigger() {
        HashMap<JobDetail, Trigger> hashMap = new HashMap<>();

        JobDetail job1 = JobBuilder.newJob(HelloJob1.class).withIdentity("job1", "group1").build();
        Trigger trigger1 = TriggerBuilder.newTrigger().withIdentity("trigger1", "group1").startNow()
                .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(5).repeatForever()).build();
        hashMap.put(job1, trigger1);

        JobDetail job2 = JobBuilder.newJob(HelloJob2.class).withIdentity("job2", "group1").build();
        Trigger trigger2 = TriggerBuilder.newTrigger().forJob("job2", "group1").startNow()
                .withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(5)).build();
        hashMap.put(job2, trigger2);

        JobDetail job3 = JobBuilder.newJob(HelloJob3.class).withIdentity("job3", "group1").build();
        Trigger trigger3 = TriggerBuilder.newTrigger().forJob("job3", "group1").startNow()
                .withSchedule(CronScheduleBuilder.cronSchedule("0/5 * * * * ?")).build();
        hashMap.put(job3,trigger3);

        return hashMap;
    }
}
quartzTest

JobDetail、Trigger、Scheduler三个对象为Quartz主要对象。启动Trigger的定时方式由不同的ScheduleBuilder子类提供,如:SimpleScheduleBuilder、CronScheduleBuilder、DailyTimeIntervalScheduleBuilder、CalendarIntervalScheduleBuilder。

spring 集成 quartz

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>cn.boomoom.maven</groupId>
  <artifactId>quartz_spring</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>war</packaging>
  <name>quartz_spring</name>
  
  <dependencies>
          <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz</artifactId>
            <version>2.2.1</version>
        </dependency>
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz-jobs</artifactId>
            <version>2.2.1</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.12</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>4.1.7.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
            <version>4.1.7.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>4.1.7.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>4.1.7.RELEASE</version>
        </dependency>
  </dependencies>
  <build>
      <plugins>
          <plugin>
              <groupId>org.codehaus.mojo</groupId>
              <artifactId>tomcat-maven-plugin</artifactId>
              <version>1.1</version>
              <configuration>
                  <port>9888</port>
              </configuration>
          </plugin>
      </plugins>
  </build>
</project>
1、pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://java.sun.com/xml/ns/javaee"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    id="WebApp_ID" version="2.5">
    
    <!-- spring配置文件位置 -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:applicationContext.xml</param-value>
    </context-param>
    <!-- spring核心监听器 -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

</web-app>
2、web.xml
package cn.boomoom.job;

import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.quartz.QuartzJobBean;

import cn.boomoom.service.HelloService;

public class HelloJob extends QuartzJobBean {

    @Autowired
    private HelloService helloService;

    @Override
    protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
        helloService.sayHello();
    }
}
HelloJob
package cn.boomoom.service;

import org.springframework.stereotype.Service;

@Service
public class HelloService {
    public void sayHello() {
        System.out.println("hello,quartz service !");
    }
}
HelloService
package cn.boomoom.job;

import org.quartz.spi.TriggerFiredBundle;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.scheduling.quartz.AdaptableJobFactory;
import org.springframework.stereotype.Service;

@Service("jobFactory")
public class JobFactory extends AdaptableJobFactory {

    @Autowired
    private AutowireCapableBeanFactory capableBeanFactory;

    @Override
    protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
        // 调用父类的方法
        Object jobInstance = super.createJobInstance(bundle);
        // 进行注入
        capableBeanFactory.autowireBean(jobInstance);
        return jobInstance;
    }
}
JobFactory
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context.xsd ">

    <context:component-scan base-package="cn.boomoom" />

    <bean id="helloJob" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
        <!--  Job实现类的全路径 -->
        <property name="jobClass" value="cn.boomoom.job.HelloJob"/>  
        <property name="jobDataAsMap">  
             <map>  
                <entry key="timeout" value="5"/>
             </map>  
        </property>  
    </bean> 

    <!-- ======================== 调度触发器 ======================== -->
    <bean id="cronTriggerBean" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
        <property name="jobDetail" ref="helloJob"></property>
        <property name="cronExpression" value="0 54 * * * ?"></property>
    </bean>

    <!-- ======================== 调度工厂 ======================== -->
    <!-- Scheduler自动运行,不用定义id -->
    <bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
        <!-- 在Job中spring管理的Bean无法注入,需要在Scheduler中自定义JobFactory -->
        <property name="jobFactory" ref="jobFactory" />
        <property name="triggers">
            <list>
                <ref bean="cronTriggerBean" />
            </list>
        </property>
    </bean>

</beans>
4、applicationContext.xml

spring提供了JobDetail、Trigger、Scheduler三个的factoryBean。

在 Job 中 spring 管理的 Bean 无法注入解决,需要在 Scheduler 中自定义 JobFactory。jobFactory 在创建job对象的时候,为其指定了 spring自动注入的属性,(类中的@Autowired)。

JobFactory 指定定义的时候,上面的方式有些时候不行,可以用下面的方式

import org.quartz.spi.TriggerFiredBundle;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.scheduling.quartz.SpringBeanJobFactory;

@Service("jobFactory")
public final class AutowiringSpringBeanJobFactory extends SpringBeanJobFactory implements
    ApplicationContextAware {

    private transient AutowireCapableBeanFactory beanFactory;

    @Override
    public void setApplicationContext(final ApplicationContext context) {
        beanFactory = context.getAutowireCapableBeanFactory();
    }

    @Override
    protected Object createJobInstance(final TriggerFiredBundle bundle) throws Exception {
        final Object job = super.createJobInstance(bundle);
        beanFactory.autowireBean(job);
        return job;
    }
}
AutowiringSpringBeanJobFactory

 JobFactory 参考1参考2.     quartz定时任务实现只执行一次,SimpleTriggerFactoryBean

 

Spring 相关的任务调度

Spring 3.0+ 自带的任务调度实现,主要依靠TaskScheduler接口的几个实现类实现。
主要用法有以下三种:

配置文件实现

  spring-schedule.xml

<task:scheduler id="myScheduler" pool-size="10" />
<task:scheduled-tasks scheduler="myScheduler">
    <task:scheduled ref="job" method="test" cron="0 * * * * ?"/>
</task:scheduled-tasks>

 

注解实现

  spring-schedule.xml

<task:scheduler id="myScheduler" pool-size="10" />
// 启用注解
<task:annotation-driven scheduler="myScheduler"/> 
@Component
public class ScheduleTask {

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

 

代码动态添加

  spring-schedule.xml

<bean id = "myScheduler" class="org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler">
    <property name="poolSize" value="10"/>
    <property name="threadGroupName" value="myScheduler" />
    <property name="threadNamePrefix" value="-1" />
</bean>
<task:annotation-driven scheduler="myScheduler"/> 
@Component
public class Test {

    @Autowired
    private ThreadPoolTaskScheduler myScheduler;

    public void addJob(){
        myScheduler.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " run ");
            }
        } , new CronTrigger("0/5 * *  * * ? ")); //每5秒执行一次
    }
}

 

推荐阅读