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,特此感謝


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM