Java定时任务的常用实现

Java的定时任务有以下几种常用的实现方式:

1)Timer

2)ScheduledThreadPoolExecutor

3)Spring中集成Cron Quartz

接下来依次介绍这几类具体实现的方式


1. Timer

利用Java自带的定时类java.util.Timer以及java.util.TimerTask共同实现多任务的定时触发与周期性执行,主要包含以下两个方法:

void schedule(TimerTask task, long delay, long period);
void scheduleAtFixedRate(TimerTask task, long delay, long period);

其中delay表示第一次执行的延迟(毫秒),period表示周期性执行的时间间隔(毫秒)。其中,需要特别注意的是这两个方法中的period都为该任务后一次执行的起始时间与前一次执行的起始时间只差,但schedule()方法该任务后一次执行的起始时间并非固定,而是取决于前一次任务的执行耗时(如果该耗时大于period,那后一次执行必须等待前一次执行完毕后立即执行,所以并不是严格的时间间隔);反观scheduleAtFixedRate()方法后一次执行则不受前一次执行耗时的影响,因此如果前一次执行较慢,可能出现两次执行并发执行的场景。

Timer典型的用法如下:

long delay = 1000L;
long period = 5000L;
Timer timer = new Timer();
timer.schedule(new TimerTask(){
    @Override
    public void run() {
        // TODO Auto-generated method stub
    }
}, delay, period);

Timer的实现过程需要依赖内部的任务队列TaskQueue与任务线程TimerThread,其中TaskQueue以最小堆的方式实现任务优先队列,而TimerThread为单线程执行线程。因此存在的一个问题就是一旦该单线程在执行某个任务时由于某些原因hang住,那后续的其余任务执行都会受到影响。


 2. ScheduledThreadPoolExecutor

继承自ThreadPoolExecutor的java.util.concurrent.ScheduledThreadPoolExecutor也可以实现多任务的定时触发与周期性执行,并且通常是多线程执行(线程池的形式),主要包含以下两个方法:

ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit);
ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit);

其中,scheduleAtFixedRate()方法与Timer的schedule()方法是类似的,后一次执行受到前一次执行耗时的影响;但scheduleWithFixedDelay()方法中的period则表示该任务后一次执行的起始时间与前一次执行的结束时间只差,这点与Timer的scheduleAtFixedRate()方法不同

既然ScheduledThreadPoolExecutor继承自ThreadPoolExecutor,那构成线程池核心的要素仍然是一样的:

  • int corePoolSize
  • int maximumPoolSize
  • long keepAliveTime
  • BlockingQueue<Runnable> workQueue
  • ThreadFactory threadFactory
  • RejectedExecutionHandler handler

但有所不同的是,ScheduledThreadPoolExecutor允许自定义的参数仅包括corePoolSize、threadFactory和handler,而maximumPoolSize恒为Integer.MAX_VALUE,keepAliveTime恒为0,workQueue恒为new DelayedWorkQueue(),这就意味着工作队列其实是无界的,且maximumPoolSize是无用的,corePoolSize就是工作线程的总个数(不会再增加)。

public ScheduledThreadPoolExecutor(int corePoolSize,
                                       ThreadFactory threadFactory,
                                       RejectedExecutionHandler handler) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue(), threadFactory, handler);
}

由于ScheduledThreadPoolExecutor是多个线程组成的线程池,因此不容易出现由于某个耗时任务导致其余定时任务难以分配线程无法执行的情况,因此更建议使用ScheduledThreadPoolExecutor取代Timer。


 3. Cron Quartz

 如果是一个Spring项目,则不妨使用Cron Quartz来更为灵活地制定定时任务,首先需要在pom.xml中增加对其的依赖配置,如下:

<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>

随后,编写一个XML配置文件(如time-task.xml),用于指定定时任务类和相应的触发时间等(配置的方法不止一种,但本质上大同小异,这里仅提供一种作为参考):

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
   <!-- 后台流量统计定时任务配置 -->
    <bean id="flowDaemon" class="com.xxx.stats.service.impl.GetFlowDaemon">
    </bean>
    <bean id="FlowDaemonDetail"
        class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
        <!--false表示等上一个任务执行完后再开启新的任务 -->
        <property name="concurrent" value="false"/>
        <property name="targetObject" ref="flowDaemon"/>
        <property name="targetMethod" value="execute"/>
    </bean>

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

    <!-- 调度工厂 -->
    <bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
        <property name="triggers">
            <list>
                <ref bean="FlowDaemonTrigger"/>
            </list>
        </property>
    </bean>
</beans>

这里假设定时任务为一个后台流量统计任务,每小时30分触发一次(配置在cronExpression中),执行的方法为com.xxx.stats.service.impl.GetFlowDaemon.execute(),且如果前一个任务延后,后一个任务也顺延(即不会并发执行)。这种配置的好处就在于所有的配置全部在XML文件中完成,而无需对Java代码有任何的改动。

类似于0 30 0/1 * * ?的定时任务时间的设定写法是非常丰富的,支持多样化的需求,详见Cron Quartz官方教程

 


 REFERENCES

[1] https://my.oschina.net/pingpangkuangmo/blog/745704

[2] http://www.cnblogs.com/hanganglin/articles/3526240.html

[3] http://www.quartz-scheduler.org/documentation/quartz-2.x/tutorials/crontrigger.html

[4] https://my.oschina.net/u/2851681/blog/744997

[5] http://www.cnblogs.com/obullxl/archive/2011/07/10/spring-quartz-cron-integration.html

 


为尊重原创成果,如需转载烦请注明本文出处:http://www.cnblogs.com/fernandolee24/p/5877516.html,特此感谢

posted @ 2016-09-26 09:41  Aπoλλων  阅读(3258)  评论(2编辑  收藏  举报