幾種任務調度的 Java 實現方法與比較1——閱讀

        綜觀目前的 Web 應用,多數應用都具有任務調度的功能。本文由淺入深介紹了幾種任務調度的 Java 實現方法,包括 Timer,Scheduler, Quartz 以及 JCron Tab,並對其優缺點進行比較,目的在於給須要開發任務調度的程序員提供有價值的參考。java

        任務調度是指基於給定時間點,給定時間間隔或者給定執行次數自動執行任務。本文由淺入深介紹四種任務調度的 Java 實現: 程序員

  • Timer併發

  • ScheduledExecutor框架

  • 開源工具包 Quartzide

  • 開源工具包 JCronTab函數

        此外,爲結合實現複雜的任務調度,本文還將介紹 Calendar 的一些使用方法。工具


Timerthis

       相信你們都已經很是熟悉 java.util.Timer 了,它是最簡單的一種實現任務調度的方法,下面給出一個具體的例子:spa

package org.js.test;

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

/**
 * Created with IntelliJ IDEA.
 * User: dev02
 * Date: 16-1-27
 * Time: 下午2:55
 * To change this template use File | Settings | File Templates.
 */
public class TimerTest extends TimerTask{
    private String jobname=null;

    public TimerTest(String jobname) {
        this.jobname = jobname;
    }

    @Override
    public void run() {
        System.out.println("job:"+jobname);
        System.out.println("sysdate:"+(new Date()));
        try {
            Thread.currentThread().sleep(4300);
        } catch (InterruptedException e) {
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        }
        System.out.println("job:"+jobname);
        System.out.println("sysdate:"+(new Date()));
        //To change body of implemented methods use File | Settings | File Templates.
    }

    public static void main(String[] args){

       Timer timer=new Timer();
       long  delay1=1*1000;
       long  period=1500;
       //1s後,每隔1.5s執行一次
       timer.schedule(new TimerTest("job1"),delay1,period);
       //2s後,每隔3s執行一次
       long  delay2=2*1000;
       long  period2=3000;
       timer.schedule(new TimerTest("job2"),delay2,period2);
    }
}

輸出結果:線程

     

 

        使用 Timer 實現任務調度的核心類是 Timer 和 TimerTask。其中 Timer 負責設定 TimerTask 的起始與間隔執行時間。使用者只須要建立一個 TimerTask 的繼承類,實現本身的 run 方法,而後將其丟給 Timer 去執行便可。

        Timer 的設計核心是一個 TaskList 和一個 TaskThread。Timer 將接收到的任務丟到本身的 TaskList 中,TaskList 按照 Task 的最初執行時間進行排序。TimerThread 在建立 Timer 時會啓動成爲一個守護線程。這個線程會輪詢全部任務,找到一個最近要執行的任務,而後休眠,當到達最近要執行任務的開始時間點,TimerThread 被喚醒並執行該任務。以後 TimerThread 更新最近一個要執行的任務,繼續休眠。

        Timer 的優勢在於簡單易用,但因爲全部任務都是由同一個線程來調度,所以全部任務都是串行執行的,同一時間只能有一個任務在執行,前一個任務的延遲或異常都將會影響到以後的任務。


ScheduledExecutor

       鑑於 Timer 的上述缺陷,Java 5 推出了基於線程池設計的 ScheduledExecutor。其設計思想是,每個被調度的任務都會由線程池中一個線程去執行,所以任務是併發執行的,相互之間不會受到干擾。需 要注意的是,只有當任務的執行時間到來時,ScheduedExecutor 纔會真正啓動一個線程,其他時間 ScheduledExecutor 都是在輪詢任務的狀態。

package org.js.test;

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

/**
 * Created with IntelliJ IDEA.
 * User: dev02
 * Date: 16-1-28
 * Time: 下午1:54
 * To change this template use File | Settings | File Templates.
 */
public class ScheduledExecutorTest implements Runnable{
    private String jobname="";

    public ScheduledExecutorTest(String jobname) {
        this.jobname = jobname;
    }

    public void run() {
        System.out.println("job:"+jobname);
        System.out.println("sysdate:"+(new Date()));
        try {
            Thread.currentThread().sleep(4300);
        } catch (InterruptedException e) {
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        }
        System.out.println("job:"+jobname);
        System.out.println("sysdate:"+(new Date()));
        //To change body of implemented methods use File | Settings | File Templates.
    }

    public static void main(String[] args){
        ScheduledExecutorService services= Executors.newScheduledThreadPool(10);
        long  delay1=1;
        long  period=2;
        //1s後,每隔1s執行一次
        services.scheduleAtFixedRate( new ScheduledExecutorTest("job1"),delay1,period, TimeUnit.SECONDS);
        long  delay2=2;
        long  period2=3;
        services.scheduleAtFixedRate( new ScheduledExecutorTest("job2"),delay2,period2, TimeUnit.SECONDS);    }
}

  輸出結果:

     

         使用ScheduledExecutor能夠看出,咱們job執行的時候線程池中一個線程去執行。這和以前使用Timer的串行執行不一致。


 ScheduledExecutor 和 Calendar 實現複雜任務調度

     Timer 和 ScheduledExecutor 都僅能提供基於開始時間與重複間隔的任務調度,不能勝任更加複雜的調度需求。好比,設置每星期二的 16:38:10 執行任務。該功能使用 Timer 和 ScheduledExecutor 都不能直接實現,但咱們能夠藉助 Calendar 間接實現該功能。 

package com.ibm.scheduler;

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

public class ScheduledExceutorTest2 extends TimerTask {

    private String jobName = "";

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

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

    /**
     * 計算從當前時間currentDate開始,知足條件dayOfWeek, hourOfDay, 
     * minuteOfHour, secondOfMinite的最近時間
     * @return
     */
    public Calendar getEarliestDate(Calendar currentDate, int dayOfWeek,
            int hourOfDay, int minuteOfHour, int secondOfMinite) {
        //計算當前時間的WEEK_OF_YEAR,DAY_OF_WEEK, HOUR_OF_DAY, MINUTE,SECOND等各個字段值
        int currentWeekOfYear = currentDate.get(Calendar.WEEK_OF_YEAR);
        int currentDayOfWeek = currentDate.get(Calendar.DAY_OF_WEEK);
        int currentHour = currentDate.get(Calendar.HOUR_OF_DAY);
        int currentMinute = currentDate.get(Calendar.MINUTE);
        int currentSecond = currentDate.get(Calendar.SECOND);

        //若是輸入條件中的dayOfWeek小於當前日期的dayOfWeek,則WEEK_OF_YEAR須要推遲一週
        boolean weekLater = false;
        if (dayOfWeek < currentDayOfWeek) {
            weekLater = true;
        } else if (dayOfWeek == currentDayOfWeek) {
            //當輸入條件與當前日期的dayOfWeek相等時,若是輸入條件中的
            //hourOfDay小於當前日期的
            //currentHour,則WEEK_OF_YEAR須要推遲一週    
            if (hourOfDay < currentHour) {
                weekLater = true;
            } else if (hourOfDay == currentHour) {
                 //當輸入條件與當前日期的dayOfWeek, hourOfDay相等時,
                 //若是輸入條件中的minuteOfHour小於當前日期的
                //currentMinute,則WEEK_OF_YEAR須要推遲一週
                if (minuteOfHour < currentMinute) {
                    weekLater = true;
                } else if (minuteOfHour == currentSecond) {
                     //當輸入條件與當前日期的dayOfWeek, hourOfDay, 
                     //minuteOfHour相等時,若是輸入條件中的
                    //secondOfMinite小於當前日期的currentSecond,
                    //則WEEK_OF_YEAR須要推遲一週
                    if (secondOfMinite < currentSecond) {
                        weekLater = true;
                    }
                }
            }
        }
        if (weekLater) {
            //設置當前日期中的WEEK_OF_YEAR爲當前周推遲一週
            currentDate.set(Calendar.WEEK_OF_YEAR, currentWeekOfYear + 1);
        }
        // 設置當前日期中的DAY_OF_WEEK,HOUR_OF_DAY,MINUTE,SECOND爲輸入條件中的值。
        currentDate.set(Calendar.DAY_OF_WEEK, dayOfWeek);
        currentDate.set(Calendar.HOUR_OF_DAY, hourOfDay);
        currentDate.set(Calendar.MINUTE, minuteOfHour);
        currentDate.set(Calendar.SECOND, secondOfMinite);
        return currentDate;

    }

    public static void main(String[] args) throws Exception {

        ScheduledExceutorTest2 test = new ScheduledExceutorTest2("job1");
        //獲取當前時間
        Calendar currentDate = Calendar.getInstance();
        long currentDateLong = currentDate.getTime().getTime();
        System.out.println("Current Date = " + currentDate.getTime().toString());
        //計算知足條件的最近一次執行時間
        Calendar earliestDate = test
                .getEarliestDate(currentDate, 3, 16, 38, 10);
        long earliestDateLong = earliestDate.getTime().getTime();
        System.out.println("Earliest Date = "
                + earliestDate.getTime().toString());
        //計算從當前時間到最近一次執行時間的時間間隔
        long delay = earliestDateLong - currentDateLong;
        //計算執行週期爲一星期
        long period = 7 * 24 * 60 * 60 * 1000;
        ScheduledExecutorService service = Executors.newScheduledThreadPool(10);
        //從如今開始delay毫秒以後,每隔一星期執行一次job1
        service.scheduleAtFixedRate(test, delay, period,
                TimeUnit.MILLISECONDS);

    }
}
/**
輸出結果:
Current Date = Wed Feb 02 17:32:01 CST 2011
Earliest Date = Tue Feb 8 16:38:10 CST 2011
Date = Tue Feb 8 16:38:10 CST 2011, execute job1
Date = Tue Feb 15 16:38:10 CST 2011, execute job1
*/

        實現了每星期二 16:38:10 調度任務的功能。其核心在於根據當前時間推算出最近一個星期二 16:38:10 的絕對時間,而後計算與當前時間的時間差,做爲調用 ScheduledExceutor 函數的參數。計算最近時間要用到 java.util.calendar 的功能。首先須要解釋 calendar 的一些設計思想。Calendar 有如下幾種惟一標識一個日期的組合方式:

YEAR + MONTH + DAY_OF_MONTH 
 YEAR + MONTH + WEEK_OF_MONTH + DAY_OF_WEEK 
 YEAR + MONTH + DAY_OF_WEEK_IN_MONTH + DAY_OF_WEEK 
 YEAR + DAY_OF_YEAR 
 YEAR + DAY_OF_WEEK + WEEK_OF_YEAR


        上述組合分別加上 HOUR_OF_DAY + MINUTE + SECOND 即爲一個完整的時間標識。本例採用了最後一種組合方式。輸入爲 DAY_OF_WEEK, HOUR_OF_DAY, MINUTE, SECOND 以及當前日期 , 輸出爲一個知足 DAY_OF_WEEK, HOUR_OF_DAY, MINUTE, SECOND 而且距離當前日期最近的將來日期。計算的原則是從輸入的 DAY_OF_WEEK 開始比較,若是小於當前日期的 DAY_OF_WEEK,則須要向 WEEK_OF_YEAR 進一, 即將當前日期中的 WEEK_OF_YEAR 加一併覆蓋舊值;若是等於當前的 DAY_OF_WEEK, 則繼續比較 HOUR_OF_DAY;若是大於當前的 DAY_OF_WEEK,則直接調用 java.util.calenda 的 calendar.set(field, value) 函數將當前日期的 DAY_OF_WEEK, HOUR_OF_DAY, MINUTE, SECOND 賦值爲輸入值,依次類推,直到比較至 SECOND。讀者能夠根據輸入需求選擇不一樣的組合方式來計算最近執行時間。

        能夠看出,用上述方法實現該任務調度比較麻煩,這就須要一個更加完善的任務調度框架來解決這些複雜的調度問題。幸運的是,開源工具包 Quartz 與 JCronTab 提供了這方面強大的支持。

相關文章
相關標籤/搜索