zoukankan      html  css  js  c++  java
  • 几种任务调度的 Java 实现方法与比较

    综观目前的 Web 应用,多数应用都具备任务调度的功能。本文由浅入深介绍了几种任务调度的 Java 实现方法,包括 Timer,Scheduler, Quartz 以及 JCron Tab,并对其优缺点进行比较,目的在于给需要开发任务调度的程序员提供有价值的参考。

    前言

    任务调度是指基于给定时间点,给定时间间隔或者给定执行次数自动执行任务。本文由浅入深介绍四种任务调度的 Java 实现:

    • Timer
    • ScheduledExecutor
    • 开源工具包 Quartz
    • 开源工具包 JCronTab

    此外,为结合实现复杂的任务调度,本文还将介绍 Calendar 的一些使用方法。

    Timer

    相信大家都已经非常熟悉 java.util.Timer 了,它是最简单的一种实现任务调度的方法,下面给出一个具体的例子:

     1 package com.ibm.scheduler; 
     2  import java.util.Timer; 
     3  import java.util.TimerTask; 
     4  
     5  public class TimerTest extends TimerTask { 
     6  
     7  private String jobName = ""; 
     8  
     9  public TimerTest(String jobName) { 
    10  super(); 
    11  this.jobName = jobName; 
    12  } 
    13  
    14  @Override
    15  public void run() { 
    16  System.out.println("execute " + jobName); 
    17  } 
    18  
    19  public static void main(String[] args) { 
    20  Timer timer = new Timer(); 
    21  long delay1 = 1 * 1000; 
    22  long period1 = 1000; 
    23  // 从现在开始 1 秒钟之后,每隔 1 秒钟执行一次 job1 
    24  timer.schedule(new TimerTest("job1"), delay1, period1); 
    25  long delay2 = 2 * 1000; 
    26  long period2 = 2000; 
    27  // 从现在开始 2 秒钟之后,每隔 2 秒钟执行一次 job2 
    28  timer.schedule(new TimerTest("job2"), delay2, period2); 
    29  } 
    30  } 
    31 /**
    32  输出结果: 
    33  execute job1 
    34  execute job1 
    35  execute job2 
    36  execute job1 
    37  execute job1 
    38  execute job2 
    39 */

    使用 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 都是在轮询任务的状态。

     1 package com.ibm.scheduler;
     2 import java.util.concurrent.Executors;
     3 import java.util.concurrent.ScheduledExecutorService;
     4 import java.util.concurrent.TimeUnit;
     5  
     6 public class ScheduledExecutorTest implements Runnable {
     7     private String jobName = "";
     8  
     9     public ScheduledExecutorTest(String jobName) {
    10         super();
    11         this.jobName = jobName;
    12     }
    13  
    14     @Override
    15     public void run() {
    16         System.out.println("execute " + jobName);
    17     }
    18  
    19     public static void main(String[] args) {
    20         ScheduledExecutorService service = Executors.newScheduledThreadPool(10);
    21  
    22         long initialDelay1 = 1;
    23         long period1 = 1;
    24         // 从现在开始1秒钟之后,每隔1秒钟执行一次job1
    25         service.scheduleAtFixedRate(
    26                 new ScheduledExecutorTest("job1"), initialDelay1,
    27                 period1, TimeUnit.SECONDS);
    28  
    29         long initialDelay2 = 1;
    30         long delay2 = 1;
    31         // 从现在开始2秒钟之后,每隔2秒钟执行一次job2
    32         service.scheduleWithFixedDelay(
    33                 new ScheduledExecutorTest("job2"), initialDelay2,
    34                 delay2, TimeUnit.SECONDS);
    35     }
    36 }
    37 /**
    38 输出结果:
    39 execute job1
    40 execute job1
    41 execute job2
    42 execute job1
    43 execute job1
    44 execute job2
    45 */

    上面代码展示了 ScheduledExecutorService 中两种最常用的调度方法 ScheduleAtFixedRate 和 ScheduleWithFixedDelay。ScheduleAtFixedRate 每次执行时间为上一次任务开始起向后推一个时间间隔,即每次执行时间为 :initialDelay, initialDelay+period, initialDelay+2*period, …;ScheduleWithFixedDelay 每次执行时间为上一次任务结束起向后推一个时间间隔,即每次执行时间为:initialDelay, initialDelay+executeTime+delay, initialDelay+2*executeTime+2*delay。由此可见,ScheduleAtFixedRate 是基于固定时间间隔进行任务调度,ScheduleWithFixedDelay 取决于每次任务执行的时间长短,是基于不固定时间间隔进行任务调度。  

    用 ScheduledExecutor 和 Calendar 实现复杂任务调度

    Timer 和 ScheduledExecutor 都仅能提供基于开始时间与重复间隔的任务调度,不能胜任更加复杂的调度需求。比如,设置每星期二的 16:38:10 执行任务。该功能使用 Timer 和 ScheduledExecutor 都不能直接实现,但我们可以借助 Calendar 间接实现该功能。

      1 package com.ibm.scheduler;
      2  
      3 import java.util.Calendar;
      4 import java.util.Date;
      5 import java.util.TimerTask;
      6 import java.util.concurrent.Executors;
      7 import java.util.concurrent.ScheduledExecutorService;
      8 import java.util.concurrent.TimeUnit;
      9  
     10 public class ScheduledExceutorTest2 extends TimerTask {
     11  
     12     private String jobName = "";
     13  
     14     public ScheduledExceutorTest2(String jobName) {
     15         super();
     16         this.jobName = jobName;
     17     }
     18  
     19     @Override
     20     public void run() {
     21         System.out.println("Date = "+new Date()+", execute " + jobName);
     22     }
     23  
     24     /**
     25      * 计算从当前时间currentDate开始,满足条件dayOfWeek, hourOfDay, 
     26      * minuteOfHour, secondOfMinite的最近时间
     27      * @return
     28      */
     29     public Calendar getEarliestDate(Calendar currentDate, int dayOfWeek,
     30             int hourOfDay, int minuteOfHour, int secondOfMinite) {
     31         //计算当前时间的WEEK_OF_YEAR,DAY_OF_WEEK, HOUR_OF_DAY, MINUTE,SECOND等各个字段值
     32         int currentWeekOfYear = currentDate.get(Calendar.WEEK_OF_YEAR);
     33         int currentDayOfWeek = currentDate.get(Calendar.DAY_OF_WEEK);
     34         int currentHour = currentDate.get(Calendar.HOUR_OF_DAY);
     35         int currentMinute = currentDate.get(Calendar.MINUTE);
     36         int currentSecond = currentDate.get(Calendar.SECOND);
     37  
     38         //如果输入条件中的dayOfWeek小于当前日期的dayOfWeek,则WEEK_OF_YEAR需要推迟一周
     39         boolean weekLater = false;
     40         if (dayOfWeek < currentDayOfWeek) {
     41             weekLater = true;
     42         } else if (dayOfWeek == currentDayOfWeek) {
     43             //当输入条件与当前日期的dayOfWeek相等时,如果输入条件中的
     44             //hourOfDay小于当前日期的
     45             //currentHour,则WEEK_OF_YEAR需要推迟一周   
     46             if (hourOfDay < currentHour) {
     47                 weekLater = true;
     48             } else if (hourOfDay == currentHour) {
     49                  //当输入条件与当前日期的dayOfWeek, hourOfDay相等时,
     50                  //如果输入条件中的minuteOfHour小于当前日期的
     51                 //currentMinute,则WEEK_OF_YEAR需要推迟一周
     52                 if (minuteOfHour < currentMinute) {
     53                     weekLater = true;
     54                 } else if (minuteOfHour == currentSecond) {
     55                      //当输入条件与当前日期的dayOfWeek, hourOfDay, 
     56                      //minuteOfHour相等时,如果输入条件中的
     57                     //secondOfMinite小于当前日期的currentSecond,
     58                     //则WEEK_OF_YEAR需要推迟一周
     59                     if (secondOfMinite < currentSecond) {
     60                         weekLater = true;
     61                     }
     62                 }
     63             }
     64         }
     65         if (weekLater) {
     66             //设置当前日期中的WEEK_OF_YEAR为当前周推迟一周
     67             currentDate.set(Calendar.WEEK_OF_YEAR, currentWeekOfYear + 1);
     68         }
     69         // 设置当前日期中的DAY_OF_WEEK,HOUR_OF_DAY,MINUTE,SECOND为输入条件中的值。
     70         currentDate.set(Calendar.DAY_OF_WEEK, dayOfWeek);
     71         currentDate.set(Calendar.HOUR_OF_DAY, hourOfDay);
     72         currentDate.set(Calendar.MINUTE, minuteOfHour);
     73         currentDate.set(Calendar.SECOND, secondOfMinite);
     74         return currentDate;
     75  
     76     }
     77  
     78     public static void main(String[] args) throws Exception {
     79  
     80         ScheduledExceutorTest2 test = new ScheduledExceutorTest2("job1");
     81         //获取当前时间
     82         Calendar currentDate = Calendar.getInstance();
     83         long currentDateLong = currentDate.getTime().getTime();
     84         System.out.println("Current Date = " + currentDate.getTime().toString());
     85         //计算满足条件的最近一次执行时间
     86         Calendar earliestDate = test
     87                 .getEarliestDate(currentDate, 3, 16, 38, 10);
     88         long earliestDateLong = earliestDate.getTime().getTime();
     89         System.out.println("Earliest Date = "
     90                 + earliestDate.getTime().toString());
     91         //计算从当前时间到最近一次执行时间的时间间隔
     92         long delay = earliestDateLong - currentDateLong;
     93         //计算执行周期为一星期
     94         long period = 7 * 24 * 60 * 60 * 1000;
     95         ScheduledExecutorService service = Executors.newScheduledThreadPool(10);
     96         //从现在开始delay毫秒之后,每隔一星期执行一次job1
     97         service.scheduleAtFixedRate(test, delay, period,
     98                 TimeUnit.MILLISECONDS);
     99  
    100     }
    101 } 
    102 /**
    103 输出结果:
    104 Current Date = Wed Feb 02 17:32:01 CST 2011
    105 Earliest Date = Tue Feb 8 16:38:10 CST 2011
    106 Date = Tue Feb 8 16:38:10 CST 2011, execute job1
    107 Date = Tue Feb 15 16:38:10 CST 2011, execute job1
    108 */

    清单 3 实现了每星期二 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 提供了这方面强大的支持。

    Quartz

    Quartz 可以满足更多更复杂的调度需求,首先让我们看看如何用 Quartz 实现每星期二 16:38 的调度安排:

     1 package com.ibm.scheduler;
     2 import java.util.Date;
     3  
     4 import org.quartz.Job;
     5 import org.quartz.JobDetail;
     6 import org.quartz.JobExecutionContext;
     7 import org.quartz.JobExecutionException;
     8 import org.quartz.Scheduler;
     9 import org.quartz.SchedulerFactory;
    10 import org.quartz.Trigger;
    11 import org.quartz.helpers.TriggerUtils;
    12  
    13 public class QuartzTest implements Job {
    14  
    15     @Override
    16     //该方法实现需要执行的任务
    17     public void execute(JobExecutionContext arg0) throws JobExecutionException {
    18         System.out.println("Generating report - "
    19                 + arg0.getJobDetail().getFullName() + ", type ="
    20                 + arg0.getJobDetail().getJobDataMap().get("type"));
    21         System.out.println(new Date().toString());
    22     }
    23     public static void main(String[] args) {
    24         try {
    25             // 创建一个Scheduler
    26             SchedulerFactory schedFact = 
    27             new org.quartz.impl.StdSchedulerFactory();
    28             Scheduler sched = schedFact.getScheduler();
    29             sched.start();
    30             // 创建一个JobDetail,指明name,groupname,以及具体的Job类名,
    31             //该Job负责定义需要执行任务
    32             JobDetail jobDetail = new JobDetail("myJob", "myJobGroup",
    33                     QuartzTest.class);
    34             jobDetail.getJobDataMap().put("type", "FULL");
    35             // 创建一个每周触发的Trigger,指明星期几几点几分执行
    36             Trigger trigger = TriggerUtils.makeWeeklyTrigger(3, 16, 38);
    37             trigger.setGroup("myTriggerGroup");
    38             // 从当前时间的下一秒开始执行
    39             trigger.setStartTime(TriggerUtils.getEvenSecondDate(new Date()));
    40             // 指明trigger的name
    41             trigger.setName("myTrigger");
    42             // 用scheduler将JobDetail与Trigger关联在一起,开始调度任务
    43             sched.scheduleJob(jobDetail, trigger);
    44  
    45         } catch (Exception e) {
    46             e.printStackTrace();
    47         }
    48     }
    49 } 
    50 /**
    51 输出结果:
    52 Generating report - myJobGroup.myJob, type =FULL
    53 Tue Feb 8 16:38:00 CST 2011
    54 Generating report - myJobGroup.myJob, type =FUL
    55 Tue Feb 15 16:38:00 CST 2011
    56 */

    清单 4 非常简洁地实现了一个上述复杂的任务调度。Quartz 设计的核心类包括 Scheduler, Job 以及 Trigger。其中,Job 负责定义需要执行的任务,Trigger 负责设置调度策略,Scheduler 将二者组装在一起,并触发任务开始执行。

    原文转自:IBM developerWorks

  • 相关阅读:
    Oracle数据导入导出脚本示例 Yang
    Golang基本数据类型 Yang
    SQL数据库使用游标示例 Yang
    Oracle数据库使用游标简单示例 Yang
    Oracle存储过程简单示例 Yang
    利用Excel内置功能快速导出数据到Excel
    程序间相互通讯问题的解决
    C#动态方法调用
    修改的一个导出DataSet到xls的单元
    Excel To SqlServer
  • 原文地址:https://www.cnblogs.com/ld-swust/p/5653870.html
Copyright © 2011-2022 走看看