精华内容
下载资源
问答
  • Java任务调度

    2021-02-28 18:40:47
    四种任务调度Java 实现:TimerScheduledExecutor开源工具包 Quartz开源工具包 JCronTabTimer相信大家都已经非常熟悉 java.util.Timer 了,它是最简单的一种实现任务调度的方法,下面给出一个具体的例子:清单 1....

    四种任务调度的 Java 实现:Timer

    ScheduledExecutor

    开源工具包 Quartz

    开源工具包 JCronTab

    Timer

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

    清单 1. 使用 Timer 进行任务调度

    package com.ibm.scheduler;

    import java.util.Timer;

    import java.util.TimerTask;

    public class TimerTest extends TimerTask {

    private String jobName = "";

    public TimerTest(String jobName) {

    super();

    this.jobName = jobName;

    }

    @Override

    public void run() {

    System.out.println("execute " + jobName);

    }

    public static void main(String[] args) {

    Timer timer = new Timer();

    long delay1 = 1 * 1000;

    long period1 = 1000;

    // 从现在开始 1 秒钟之后,每隔 1 秒钟执行一次 job1

    timer.schedule(new TimerTest("job1"), delay1, period1);

    long delay2 = 2 * 1000;

    long period2 = 2000;

    // 从现在开始 2 秒钟之后,每隔 2 秒钟执行一次 job2

    timer.schedule(new TimerTest("job2"), delay2, period2);

    }

    }

    Output:

    execute job1

    execute job1

    execute job2

    execute job1

    execute job1

    execute job2

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

    清单 2. 使用 ScheduledExecutor 进行任务调度

    package com.ibm.scheduler;

    import java.util.concurrent.Executors;

    import java.util.concurrent.ScheduledExecutorService;

    import java.util.concurrent.TimeUnit;

    public class ScheduledExecutorTest implements Runnable {

    private String jobName = "";

    public ScheduledExecutorTest(String jobName) {

    super();

    this.jobName = jobName;

    }

    @Override

    public void run() {

    System.out.println("execute " + jobName);

    }

    public static void main(String[] args) {

    ScheduledExecutorService service = Executors.newScheduledThreadPool(10);

    long initialDelay1 = 1;

    long period1 = 1;

    // 从现在开始1秒钟之后,每隔1秒钟执行一次job1

    service.scheduleAtFixedRate(

    new ScheduledExecutorTest("job1"), initialDelay1,

    period1, TimeUnit.SECONDS);

    long initialDelay2 = 1;

    long delay2 = 1;

    // 从现在开始2秒钟之后,每隔2秒钟执行一次job2

    service.scheduleWithFixedDelay(

    new ScheduledExecutorTest("job2"), initialDelay2,

    delay2, TimeUnit.SECONDS);

    }

    }

    Output:

    execute job1

    execute job1

    execute job2

    execute job1

    execute job1

    execute job2

    清单 2 展示了 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 间接实现该功能。

    清单 3. 使用 ScheduledExcetuor 和 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);

    }

    }

    Output:

    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

    清单 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 的调度安排:

    清单 4. 使用 Quartz 进行任务调度

    package com.ibm.scheduler;

    import java.util.Date;

    import org.quartz.Job;

    import org.quartz.JobDetail;

    import org.quartz.JobExecutionContext;

    import org.quartz.JobExecutionException;

    import org.quartz.Scheduler;

    import org.quartz.SchedulerFactory;

    import org.quartz.Trigger;

    import org.quartz.helpers.TriggerUtils;

    public class QuartzTest implements Job {

    @Override

    //该方法实现需要执行的任务

    public void execute(JobExecutionContext arg0) throws JobExecutionException {

    System.out.println("Generating report - "

    + arg0.getJobDetail().getFullName() + ", type ="

    + arg0.getJobDetail().getJobDataMap().get("type"));

    System.out.println(new Date().toString());

    }

    public static void main(String[] args) {

    try {

    // 创建一个Scheduler

    SchedulerFactory schedFact =

    new org.quartz.impl.StdSchedulerFactory();

    Scheduler sched = schedFact.getScheduler();

    sched.start();

    // 创建一个JobDetail,指明name,groupname,以及具体的Job类名,

    //该Job负责定义需要执行任务

    JobDetail jobDetail = new JobDetail("myJob", "myJobGroup",

    QuartzTest.class);

    jobDetail.getJobDataMap().put("type", "FULL");

    // 创建一个每周触发的Trigger,指明星期几几点几分执行

    Trigger trigger = TriggerUtils.makeWeeklyTrigger(3, 16, 38);

    trigger.setGroup("myTriggerGroup");

    // 从当前时间的下一秒开始执行

    trigger.setStartTime(TriggerUtils.getEvenSecondDate(new Date()));

    // 指明trigger的name

    trigger.setName("myTrigger");

    // 用scheduler将JobDetail与Trigger关联在一起,开始调度任务

    sched.scheduleJob(jobDetail, trigger);

    } catch (Exception e) {

    e.printStackTrace();

    }

    }

    }

    Output:

    Generating report - myJobGroup.myJob, type =FULL

    Tue Feb 8 16:38:00 CST 2011

    Generating report - myJobGroup.myJob, type =FULL

    Tue Feb 15 16:38:00 CST 2011

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

    Job

    使用者只需要创建一个 Job 的继承类,实现 execute 方法。JobDetail 负责封装 Job 以及 Job 的属性,并将其提供给 Scheduler 作为参数。每次 Scheduler 执行任务时,首先会创建一个 Job 的实例,然后再调用 execute 方法执行。Quartz 没有为 Job 设计带参数的构造函数,因此需要通过额外的 JobDataMap 来存储 Job 的属性。JobDataMap 可以存储任意数量的 Key,Value 对,例如:

    清单 5. 为 JobDataMap 赋值

    jobDetail.getJobDataMap().put("myDescription", "my job description");

    jobDetail.getJobDataMap().put("myValue", 1998);

    ArrayList list = new ArrayList();

    list.add("item1");

    jobDetail.getJobDataMap().put("myArray", list);

    JobDataMap 中的数据可以通过下面的方式获取:

    清单 6. 获取 JobDataMap 的值

    public class JobDataMapTest implements Job {

    @Override

    public void execute(JobExecutionContext context)

    throws JobExecutionException {

    //从context中获取instName,groupName以及dataMap

    String instName = context.getJobDetail().getName();

    String groupName = context.getJobDetail().getGroup();

    JobDataMap dataMap = context.getJobDetail().getJobDataMap();

    //从dataMap中获取myDescription,myValue以及myArray

    String myDescription = dataMap.getString("myDescription");

    int myValue = dataMap.getInt("myValue");

    ArrayList myArray = (ArrayListlt;Strin>) dataMap.get("myArray");

    System.out.println("

    Instance =" + instName + ", group = " + groupName

    + ", description = " + myDescription + ", value =" + myValue

    + ", array item0 = " + myArray.get(0));

    }

    }

    Output:

    Instance = myJob, group = myJobGroup,

    description = my job description,

    value =1998, array item0 = item1

    Trigger

    Trigger 的作用是设置调度策略。Quartz 设计了多种类型的 Trigger,其中最常用的是 SimpleTrigger 和 CronTrigger。

    SimpleTrigger 适用于在某一特定的时间执行一次,或者在某一特定的时间以某一特定时间间隔执行多次。上述功能决定了 SimpleTrigger 的参数包括 start-time, end-time, repeat count, 以及 repeat interval。

    Repeat count 取值为大于或等于零的整数,或者常量 SimpleTrigger.REPEAT_INDEFINITELY。

    Repeat interval 取值为大于或等于零的长整型。当 Repeat interval 取值为零并且 Repeat count 取值大于零时,将会触发任务的并发执行。

    Start-time 与 dnd-time 取值为 java.util.Date。当同时指定 end-time 与 repeat count 时,优先考虑 end-time。一般地,可以指定 end-time,并设定 repeat count 为 REPEAT_INDEFINITELY。

    以下是 SimpleTrigger 的构造方法:

    public SimpleTrigger(String name,

    String group,

    Date startTime,

    Date endTime,

    int repeatCount,

    long repeatInterval)

    举例如下:

    创建一个立即执行且仅执行一次的 SimpleTrigger:

    SimpleTrigger trigger=

    new SimpleTrigger("myTrigger", "myGroup", new Date(), null, 0, 0L);

    创建一个半分钟后开始执行,且每隔一分钟重复执行一次的 SimpleTrigger:

    SimpleTrigger trigger=

    new SimpleTrigger("myTrigger", "myGroup",

    new Date(System.currentTimeMillis()+30*1000), null, 0, 60*1000);

    创建一个 2011 年 6 月 1 日 8:30 开始执行,每隔一小时执行一次,一共执行一百次,一天之后截止的 SimpleTrigger:

    Calendar calendar = Calendar.getInstance();

    calendar.set(Calendar.YEAR, 2011);

    calendar.set(Calendar.MONTH, Calendar.JUNE);

    calendar.set(Calendar.DAY_OF_MONTH, 1);

    calendar.set(Calendar.HOUR, 8);

    calendar.set(Calendar.MINUTE, 30);

    calendar.set(Calendar.SECOND, 0);

    calendar.set(Calendar.MILLISECOND, 0);

    Date startTime = calendar.getTime();

    Date endTime = new Date (calendar.getTimeInMillis() +24*60*60*1000);

    SimpleTrigger trigger=new SimpleTrigger("myTrigger",

    "myGroup", startTime, endTime, 100, 60*60*1000);

    上述最后一个例子中,同时设置了 end-time 与 repeat count,则优先考虑 end-time,总共可以执行二十四次。

    CronTrigger 的用途更广,相比基于特定时间间隔进行调度安排的 SimpleTrigger,CronTrigger 主要适用于基于日历的调度安排。例如:每星期二的 16:38:10 执行,每月一号执行,以及更复杂的调度安排等。

    CronTrigger 同样需要指定 start-time 和 end-time,其核心在于 Cron 表达式,由七个字段组成:

    Seconds

    Minutes

    Hours

    Day-of-Month

    Month

    Day-of-Week

    Year (Optional field)

    举例如下:

    创建一个每三小时执行的 CronTrigger,且从每小时的整点开始执行:

    0 0 0/3 * * ?

    创建一个每十分钟执行的 CronTrigger,且从每小时的第三分钟开始执行:

    0 3/10 * * * ?

    创建一个每周一,周二,周三,周六的晚上 20:00 到 23:00,每半小时执行一次的 CronTrigger:

    0 0/30 20-23 ? * MON-WED,SAT

    创建一个每月最后一个周四,中午 11:30-14:30,每小时执行一次的 trigger:

    0 30 11-14/1 ? * 5L

    解释一下上述例子中各符号的含义:

    首先所有字段都有自己特定的取值,例如,Seconds 和 Minutes 取值为 0 到 59,Hours 取值为 0 到 23,Day-of-Month 取值为 0-31, Month 取值为 0-11,或者 JAN,FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC,Days-of-Week 取值为 1-7 或者 SUN, MON, TUE, WED, THU, FRI, SAT。每个字段可以取单个值,多个值,或一个范围,例如 Day-of-Week 可取值为“MON,TUE,SAT”,“MON-FRI”或者“TUE-THU,SUN”。

    通配符 * 表示该字段可接受任何可能取值。例如 Month 字段赋值 * 表示每个月,Day-of-Week 字段赋值 * 表示一周的每天。

    / 表示开始时刻与间隔时段。例如 Minutes 字段赋值 2/10 表示在一个小时内每 20 分钟执行一次,从第 2 分钟开始。

    ? 仅适用于 Day-of-Month 和 Day-of-Week。? 表示对该字段不指定特定值。适用于需要对这两个字段中的其中一个指定值,而对另一个不指定值的情况。一般情况下,这两个字段只需对一个赋值。

    L 仅适用于 Day-of-Month 和 Day-of-Week。L 用于 Day-of-Month 表示该月最后一天。L 单独用于 Day-of-Week 表示周六,否则表示一个月最后一个星期几,例如 5L 或者 THUL 表示该月最后一个星期四。

    W 仅适用于 Day-of-Month,表示离指定日期最近的一个工作日,例如 Day-of-Month 赋值为 10W 表示该月离 10 号最近的一个工作日。

    # 仅适用于 Day-of-Week,表示该月第 XXX 个星期几。例如 Day-of-Week 赋值为 5#2 或者 THU#2,表示该月第二个星期四。

    CronTrigger 的使用如下:

    CronTrigger cronTrigger = new CronTrigger("myTrigger", "myGroup");

    try {

    cronTrigger.setCronExpression("0 0/30 20-13 ? * MON-WED,SAT");

    } catch (Exception e) {

    e.printStackTrace();

    }

    Job 与 Trigger 的松耦合设计是 Quartz 的一大特点,其优点在于同一个 Job 可以绑定多个不同的 Trigger,同一个 Trigger 也可以调度多个 Job,灵活性很强。

    Listener

    除了上述基本的调度功能,Quartz 还提供了 listener 的功能。主要包含三种 listener:JobListener,TriggerListener 以及 SchedulerListener。当系统发生故障,相关人员需要被通知时,Listener 便能发挥它的作用。最常见的情况是,当任务被执行时,系统发生故障,Listener 监听到错误,立即发送邮件给管理员。下面给出 JobListener 的实例:

    清单 7. JobListener 的实现

    import org.quartz.JobExecutionContext;

    import org.quartz.JobExecutionException;

    import org.quartz.JobListener;

    import org.quartz.SchedulerException;

    public class MyListener implements JobListener{

    @Override

    public String getName() {

    return "My Listener";

    }

    @Override

    public void jobWasExecuted(JobExecutionContext context,

    JobExecutionException jobException) {

    if(jobException != null){

    try {

    //停止Scheduler

    context.getScheduler().shutdown();

    System.out.println("

    Error occurs when executing jobs, shut down the scheduler ");

    // 给管理员发送邮件…

    } catch (SchedulerException e) {

    e.printStackTrace();

    }

    }

    }

    }

    从清单 7 可以看出,使用者只需要创建一个 JobListener 的继承类,重载需要触发的方法即可。当然,需要将 listener 的实现类注册到 Scheduler 和 JobDetail 中:

    sched.addJobListener(new MyListener());

    jobDetail.addJobListener("My Listener"); // listener 的名字

    使用者也可以将 listener 注册为全局 listener,这样便可以监听 scheduler 中注册的所有任务 :

    sched.addGlobalJobListener(new MyListener());

    为了测试 listener 的功能,可以在 job 的 execute 方法中强制抛出异常。清单 7 中,listener 接收到异常,将 job 所在的 scheduler 停掉,阻止后续的 job 继续执行。scheduler、jobDetail 等信息都可以从 listener 的参数 context 中检索到。

    清单 7 的输出结果为:

    Generating report - myJob.myJob, type =FULL

    Tue Feb 15 18:57:35 CST 2011

    2011-2-15 18:57:35 org.quartz.core.JobRunShell run

    信息 : Job myJob.myJob threw a JobExecutionException:

    org.quartz.JobExecutionException

    at com.ibm.scheduler.QuartzListenerTest.execute(QuartzListenerTest.java:22)

    at org.quartz.core.JobRunShell.run(JobRunShell.java:191)

    at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:516)

    2011-2-15 18:57:35 org.quartz.core.QuartzScheduler shutdown

    信息 : Scheduler DefaultQuartzScheduler_$_NON_CLUSTERED shutting down.

    Error occurs when executing jobs, shut down the scheduler

    TriggerListener、SchedulerListener 与 JobListener 有类似的功能,只是各自触发的事件不同,如 JobListener 触发的事件为:

    Job to be executed, Job has completed execution 等

    TriggerListener 触发的事件为:

    Trigger firings, trigger mis-firings, trigger completions 等

    SchedulerListener 触发的事件为:

    add a job/trigger, remove a job/trigger, shutdown a scheduler 等

    读者可以根据自己的需求重载相应的事件。

    JobStores

    Quartz 的另一显著优点在于持久化,即将任务调度的相关数据保存下来。这样,当系统重启后,任务被调度的状态依然存在于系统中,不会丢失。默认情况下,Quartz 采用的是 org.quartz.simpl.RAMJobStore,在这种情况下,数据仅能保存在内存中,系统重启后会全部丢失。若想持久化数据,需要采用 org.quartz.simpl.JDBCJobStoreTX。

    实现持久化的第一步,是要创建 Quartz 持久化所需要的表格。在 Quartz 的发布包 docs/dbTables 中可以找到相应的表格创建脚本。Quartz 支持目前大部分流行的数据库。本文以 DB2 为例,所需要的脚本为 tables_db2.sql。首先需要对脚本做一点小的修改,即在开头指明 Schema:

    SET CURRENT SCHEMA quartz;

    为了方便重复使用 , 创建表格前首先删除之前的表格:

    drop table qrtz_job_details;

    drop table qrtz_job_listeners;

    然后创建数据库 sched,执行 tables_db2.sql 创建持久化所需要的表格。

    第二步,配置数据源。数据源与其它所有配置,例如 ThreadPool,均放在 quartz.properties 里:

    清单 8. Quartz 配置文件

    # Configure ThreadPool

    org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool

    org.quartz.threadPool.threadCount = 5

    org.quartz.threadPool.threadPriority = 4

    # Configure Datasources

    org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX

    org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate

    org.quartz.jobStore.dataSource = db2DS

    org.quartz.jobStore.tablePrefix = QRTZ_

    org.quartz.dataSource.db2DS.driver = com.ibm.db2.jcc.DB2Driver

    org.quartz.dataSource.db2DS.URL = jdbc:db2://localhost:50001/sched

    org.quartz.dataSource.db2DS.user = quartz

    org.quartz.dataSource.db2DS.password = passw0rd

    org.quartz.dataSource.db2DS.maxConnections = 5

    使用时只需要将 quatz.properties 放在 classpath 下面,不用更改一行代码,再次运行之前的任务调度实例,trigger、job 等信息便会被记录在数据库中。

    将清单 4 中的 makeWeeklyTrigger 改成 makeSecondlyTrigger,重新运行 main 函数,在 sched 数据库中查询表 qrtz_simple_triggers 中的数据。其查询语句为“db2 ‘ select repeat_interval, times_triggered from qrtz_simple_triggers ’”。结果 repeat_interval 为 1000,与程序中设置的 makeSecondlyTrigger 相吻合,times_triggered 值为 21。

    停掉程序,将数据库中记录的任务调度数据重新导入程序运行:

    清单 9. 从数据库中导入任务调度数据重新运行

    package com.ibm.scheduler;

    import org.quartz.Scheduler;

    import org.quartz.SchedulerException;

    import org.quartz.SchedulerFactory;

    import org.quartz.Trigger;

    import org.quartz.impl.StdSchedulerFactory;

    public class QuartzReschedulerTest {

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

    // 初始化一个 Schedule Factory

    SchedulerFactory schedulerFactory = new StdSchedulerFactory();

    // 从 schedule factory 中获取 scheduler

    Scheduler scheduler = schedulerFactory.getScheduler();

    // 从 schedule factory 中获取 trigger

    Trigger trigger = scheduler.getTrigger("myTrigger", "myTriggerGroup");

    // 重新开启调度任务

    scheduler.rescheduleJob("myTrigger", "myTriggerGroup", trigger);

    scheduler.start();

    }

    }

    清单 9 中,schedulerFactory.getScheduler() 将 quartz.properties 的内容加载到内存,然后根据数据源的属性初始化数据库的链接,并将数据库中存储的数据加载到内存。之后,便可以在内存中查询某一具体的 trigger,并将其重新启动。这时候重新查询 qrtz_simple_triggers 中的数据,发现 times_triggered 值比原来增长了。

    JCronTab

    习惯使用 unix/linux 的开发人员应该对 crontab 都不陌生。Crontab 是一个非常方便的用于 unix/linux 系统的任务调度命令。JCronTab 则是一款完全按照 crontab 语法编写的 java 任务调度工具。

    首先简单介绍一下 crontab 的语法,与上面介绍的 Quartz 非常相似,但更加简洁 , 集中了最常用的语法。主要由六个字段组成(括弧中标识了每个字段的取值范围):

    Minutes (0-59)

    Hours (0-23)

    Day-of-Month (1-31)

    Month (1-12/JAN-DEC)

    Day-of-Week (0-6/SUN-SAT)

    Command

    与 Quartz 相比,省略了 Seconds 与 Year,多了一个 command 字段,即为将要被调度的命令。JCronTab 中也包含符号“*”与“/”, 其含义与 Quartz 相同。

    举例如下:

    每天 12 点到 15 点 , 每隔 1 小时执行一次 Date 命令:

    0 12-15/1 * * * Date

    每月 2 号凌晨 1 点发一封信给 zhjingbj@cn.ibm.com:

    0 1 2 * * mail -s “good” zhjingbj@cn.ibm.com

    每周一,周二,周三,周六的晚上 20:00 到 23:00,每半小时打印“normal”:

    0/30 20-23 * * MON-WED,SAT echo “normal”

    JCronTab 借鉴了 crontab 的语法,其区别在于 command 不再是 unix/linux 的命令,而是一个 Java 类。如果该类带参数,例如“com.ibm.scheduler.JCronTask2#run”,则定期执行 run 方法;如果该类不带参数,则默认执行 main 方法。此外,还可以传参数给 main 方法或者构造函数,例如“com.ibm.scheduler.JCronTask2#run Hello World“表示传两个参数 Hello 和 World 给构造函数。

    JCronTab 与 Quartz 相比,其优点在于,第一,支持多种任务调度的持久化方法,包括普通文件、数据库以及 XML 文件进行持久化;第二,JCronTab 能够非常方便地与 Web 应用服务器相结合,任务调度可以随 Web 应用服务器的启动自动启动;第三,JCronTab 还内置了发邮件功能,可以将任务执行结果方便地发送给需要被通知的人。

    JCronTab 与 Web 应用服务器的结合非常简单,只需要在 Web 应用程序的 web.xml 中添加如下行:

    清单 10. 在 web.xml 中配置 JCronTab 的属性

    LoadOnStartupServlet

    org.jcrontab.web.loadCrontabServlet

    PROPERTIES_FILE

    D:/Scheduler/src/jcrontab.properties

    1

    LoadOnStartupServlet

    /Startup

    在清单 10 中,需要注意两点:第一,必须指定 servlet-class 为 org.jcrontab.web.loadCrontabServlet,因为它是整个任务调度的入口;第二,必须指定一个参数为 PROPERTIES_FILE,才能被 loadCrontabServlet 识别。

    接下来,需要撰写 D:/Scheduler/src/jcrontab.properties 的内容,其内容根据需求的不同而改变。

    当采用普通文件持久化时,jcrontab.properties 的内容主要包括:

    org.jcrontab.data.file = D:/Scheduler/src/crontab

    org.jcrontab.data.datasource = org.jcrontab.data.FileSource

    其中数据来源 org.jcrontab.data.datasource 被描述为普通文件,即 org.jcrontab.data.FileSource。具体的文件即 org.jcrontab.data.file 指明为 D:/Scheduler/src/crontab。

    Crontab 描述了任务的调度安排:

    */2 * * * * com.ibm.scheduler.JCronTask1

    * * * * * com.ibm.scheduler.JCronTask2#run Hello World

    其中包含了两条任务的调度,分别是每两分钟执行一次 JCronTask1 的 main 方法,每一分钟执行一次 JCronTask2 的 run 方法。

    清单 11. JcronTask1 与 JCronTask2 的实现

    package com.ibm.scheduler;

    import java.util.Date;

    public class JCronTask1 {

    private static int count = 0;

    public static void main(String[] args) {

    System.out.println("--------------Task1-----------------");

    System.out.println("Current Time = " + new Date() + ", Count = "

    + count++);

    }

    }

    package com.ibm.scheduler;

    import java.util.Date;

    public class JCronTask2 implements Runnable {

    private static int count = 0;

    private static String[] args;

    public JCronTask2(String[] args) {

    System.out.println("--------------Task2-----------------");

    System.out.println("Current Time = " + new Date() + ", Count = "

    + count++);

    JCronTask2.args = args;

    }

    @Override

    public void run() {

    System.out.println("enter into run method");

    if (args != null && args.length > 0) {

    for (int i = 0; i < args.length; i++) {

    System.out.print("This is arg " + i + " " + args[i] + "\n");

    }

    }

    }

    }

    到此为止,基于普通文件持久化的 JCronTab 的实例就全部配置好了。启动 Web 应用服务器,便可以看到任务调度的输出结果:

    --------------Task2-----------------

    Current Time = Tue Feb 15 09:22:00 CST 2011, Count = 0

    enter into run method

    This is arg 0 Hello

    This is arg 1 World

    --------------Task1-----------------

    Current Time = Tue Feb 15 09:22:00 CST 2011, Count = 0

    --------------Task2-----------------

    Current Time = Tue Feb 15 09:23:00 CST 2011, Count = 1

    enter into run method

    This is arg 0 Hello

    This is arg 1 World

    --------------Task2-----------------

    Current Time = Tue Feb 15 09:24:00 CST 2011, Count = 2

    enter into run method

    This is arg 0 Hello

    This is arg 1 World

    --------------Task1-----------------

    Current Time = Tue Feb 15 09:24:00 CST 2011, Count = 1

    通过修改 jcrontab.properties 中 datasource,可以选择采用数据库或 xml 文件持久化,感兴趣的读者可以参考 进阶学习 JCronTab。

    此外,JCronTab 还内置了发邮件功能,可以将任务执行结果方便地发送给需要被通知的人。其配置非常简单,只需要在 jcontab.properties 中添加几行配置即可:

    org.jcrontab.sendMail.to= Ther email you want to send to

    org.jcrontab.sendMail.from=The email you want to send from

    org.jcrontab.sendMail.smtp.host=smtp server

    org.jcrontab.sendMail.smtp.user=smtp username

    org.jcrontab.sendMail.smtp.password=smtp password

    结束语

    本文介绍了四种常用的对任务进行调度的 Java 实现方法,即 Timer,ScheduledExecutor, Quartz 以及 JCronTab。文本对每种方法都进行了实例解释,并对其优缺点进行比较。对于简单的基于起始时间点与时间间隔的任务调度,使用 Timer 就足够了;如果需要同时调度多个任务,基于线程池的 ScheduledTimer 是更为合适的选择;当任务调度的策略复杂到难以凭借起始时间点与时间间隔来描述时,Quartz 与 JCronTab 则体现出它们的优势。熟悉 Unix/Linux 的开发人员更倾向于 JCronTab,且 JCronTab 更适合与 Web 应用服务器相结合。Quartz 的 Trigger 与 Job 松耦合设计使其更适用于 Job 与 Trigger 的多对多应用场景。

    展开全文
  • Cron4j : A pure Java cron-like scheduler创立时间:2017年8月20日13:35:01Maven依赖:it.sauronsoftware.cron4jcron4j2.2.5前言:这是一篇针对cron4j官方英文文档的翻译。虽然官方文档并不难看懂,但是本着高尚的...

    Cron4j : A pure Java cron-like scheduler

    创立时间:2017年8月20日13:35:01

    Maven依赖:

    it.sauronsoftware.cron4j

    cron4j

    2.2.5

    前言:

    这是一篇针对cron4j官方英文文档的翻译。

    虽然官方文档并不难看懂,但是本着高尚的自学精神,再加上暑假有点无聊,我尝试着翻译这篇Java工具包的技术文档。

    该翻译并不是来自官方的,仅仅是自学用的翻译,如有翻译错误,请在issue中指出。

    本人并仅仅英语四级压线水平,所以文档并没有严格按照语法规范翻译,首先遵从本人自己的理解(如有理解错误也请在issue中指出),再尽量翻译成通俗的语句,要求的是能根据本文快速掌握cron4j工具。一切以实践为标准,我在学习的时候也会先实践,再结合实践翻译到文档中。

    在JavaWeb开发中,一些后台业务场景会有需要定时任务的需求,这些定时任务如果人工去执行的话就会显得非常蠢,所以就有了定时任务工具包/框架的出现。

    其实流行的定时任务框架就像Quartz这样的,应该是运用到生产环境中比较好的选择,但是Quartz的官方文档结构有点蛋疼(也可能是我没细看),并不能简明直接的让开发者循序渐进的掌握它。而在一些博客中对比也谈到Quartz比Cron4j臃肿一些,这也是增加学习成本的原因之意。它们的性能对比不知道怎么样,不过我猜应该是Quartz要好一点,毕竟持续到近两个月前Quartz还在继续维护当中,而Cron4j最近的发布时间是:28-Dec-2011(膜拜)。

    我为什么选择cron4j,是因为最近在学JFinal3.2,里面插件扩展的章节介绍到了cron4j,之前也有思考过定时任务的解决方案,恰好再此了解到它。在学习cron4j的过程中也确实感受到了它的“pure”之意,所以我也愿意花一些时间来去翻译它的官方文档和学习它。

    在完成文档的过程中有一点比较难受的是,我每次实践都至少要等上一分钟才能验证结果...

    Overview部分:

    cron4j是Java平台的一个调度器(也就是任务调度工具/框架),它非常像UNIX系统下的具有进程守护的定时任务工具cron。

    有了cron4j,你可以在你规定好的时间内在Java应用程序中执行你指定的任务,而这只需要你制定一些简单的规则。

    虽然Java平台已经内置了一个由java.util.Timer类实例化的调度器,但是cron4j走的是和前者不同的另一条路子。

    你可以说java.util.Timer调度器是

    “从现在开始过5分钟后启动这个任务”

    或者说

    “从现在开始过5分钟后执行这个任务,然后每10分钟重复执行它”。

    这就是java.util.Timer。

    而cron4j调度器会让你稍微多做一些复杂的事情, 比如:

    “在每个周一的12时执行这个任务”

    “每隔5分钟执行这个任务,但是周末期间可以不执行”

    “在8:00am到8:00pm之间的每个小时执行一次任务,而在8:00pm到8:00am之间的每5分钟执行一次任务”

    “除了7月和8月之外的月份内并且在一周内除了周日之外,每天都执行一次任务”

    这些蜜汁操作,想要实现它们你只需要简单的写一小行代码就可以Duang出来。

    把cron4j使用到你的项目里面其实非常简单,你只需要掌握一些常用API就足够了。启动定时任务的启动规则必须是一个字符串表达式,它被称为scheduling pattern(调度模式),它的语法等同于UNIX系统中crontab所使用的语法一样。如果你了解过UNIX中crontab的操作,那么恭喜你,你已经掌握本工具的一大半了。如果你不会,don't worry:crontab的调度模式你只需要花上几分钟就能掌握(骗人!),再说了,后面还有documentation给你学习呢。

    运行要求:

    你可以在任何Java平台使用它。

    License:

    cron4j is Free Software and it is licensed under LGPL (you will find a copy of the license bundled into the downloadable software distribution).

    Feedback

    ...

    Make a donation

    ...

    翻译进度:

    第一阶段

    时间:2017年8月20日21:03:12

    内容:Overview、doc的前三节

    第二阶段

    时间:2017年8月21日00:01:13

    内容:doc第四、五节 第六节的大部分

    第三阶段

    时间:2017年8月21日22:15:33

    内容:doc第七到末节

    展开全文
  • Java定时任务调度详解

    2020-12-19 07:36:23
    前言在实际项目开发,除了Web应用、SOA服务外,还有一类不可缺少的,那就是定时任务调度。定时任务的场景可以说非常广泛,比如某些视频网站,购买会员后,每天会给会员送成长值,每月会给会员送一些电影券;比如在...

    前言

    在实际项目开发中,除了Web应用、SOA服务外,还有一类不可缺少的,那就是定时任务调度。定时任务的场景可以说非常广泛,比如某些视频网站,购买会员后,每天会给会员送成长值,每月会给会员送一些电影券;比如在保证最终一致性的场景中,往往利用定时任务调度进行一些比对工作;比如一些定时需要生成的报表、邮件;比如一些需要定时清理数据的任务等。本篇博客将系统的介绍定时任务调度,会涵盖Timer、ScheduledExecutorService、开源工具包Quartz,以及Spring和Quartz的结合等内容。

    JDK原生定时工具:Timer

    定时任务调度:基于给定的时间点、给定的时间间隔、给定的执行次数自动执行的任务。

    Timer位于java.util包下,其内部包含且仅包含一个后台线程(TimeThread)对多个业务任务(TimeTask)进行定时定频率的调度。

    schedule的四种用法和scheduleAtFixedRate的两种用法

    参数说明:

    task:所要执行的任务,需要extends TimeTask override run()

    time/firstTime:首次执行任务的时间

    period:周期性执行Task的时间间隔,单位是毫秒

    delay:执行task任务前的延时时间,单位是毫秒

    很显然,通过上述的描述,我们可以实现:

    延迟多久后执行一次任务;指定时间执行一次任务;延迟一段时间,并周期性执行任务;指定时间,并周期性执行任务;

    思考1:如果time/firstTime指定的时间,在当前时间之前,会发生什么呢?

    在时间等于或者超过time/firstTime的时候,会执行task!也就是说,如果time/firstTime指定的时间在当前时间之前,就会立即得到执行。

    思考2:schedule和scheduleAtFixedRate有什么区别?

    scheduleAtFixedRate:每次执行时间为上一次任务开始起向后推一个period间隔,也就是说下次执行时间相对于上一次任务开始的时间点,因此执行时间不会延后,但是存在任务并发执行的问题。

    schedule:每次执行时间为上一次任务结束后推一个period间隔,也就是说下次执行时间相对于上一次任务结束的时间点,因此执行时间会不断延后。

    思考3:如果执行task发生异常,是否会影响其他task的定时调度?

    如果TimeTask抛出RuntimeException,那么Timer会停止所有任务的运行!

    思考4:Timer的一些缺陷?

    前面已经提及到Timer背后是一个单线程,因此Timer存在管理并发任务的缺陷:所有任务都是由同一个线程来调度,所有任务都是串行执行,意味着同一时间只能有一个任务得到执行,而前一个任务的延迟或者异常会影响到之后的任务。

    其次,Timer的一些调度方式还算比较简单,无法适应实际项目中任务定时调度的复杂度。

    一个简单的Demo实例

    Timer其他需要关注的方法

    cancel():终止Timer计时器,丢弃所有当前已安排的任务(TimeTask也存在cancel()方法,不过终止的是TimeTask)

    purge():从计时器的任务队列中移除已取消的任务,并返回个数

    JDK对定时任务调度的线程池支持:ScheduledExecutorService

    由于Timer存在的问题,JDK5之后便提供了基于线程池的定时任务调度:ScheduledExecutorService。

    设计理念:每一个被调度的任务都会被线程池中的一个线程去执行,因此任务可以并发执行,而且相互之间不受影响。

    我们直接看例子:

    定时任务大哥:Quartz

    虽然ScheduledExecutorService对Timer进行了线程池的改进,但是依然无法满足复杂的定时任务调度场景。因此OpenSymphony提供了强大的开源任务调度框架:Quartz。Quartz是纯Java实现,而且作为Spring的默认调度框架,由于Quartz的强大的调度功能、灵活的使用方式、还具有分布式集群能力,可以说Quartz出马,可以搞定一切定时任务调度!

    Quartz的体系结构

    先来看一个Demo:

    说明:

    1、从代码上来看,有XxxBuilder、XxxFactory,说明Quartz用到了Builder、Factory模式,还有非常易懂的链式编程风格。

    2、Quartz有3个核心概念:调度器(Scheduler)、任务(Job&JobDetail)、触发器(Trigger)。(一个任务可以被多个触发器触发,一个触发器只能触发一个任务)

    3、注意当Scheduler调度Job时,实际上会通过反射newInstance一个新的Job实例(待调度完毕后销毁掉),同时会把JobExecutionContext传递给Job的execute方法,Job实例通过JobExecutionContext访问到Quartz运行时的环境以及Job本身的明细数据。

    4、JobDataMap可以装载任何可以序列化的数据,存取很方便。需要注意的是JobDetail和Trigger都可以各自关联上JobDataMap。JobDataMap除了可以通过上述代码获取外,还可以在YourJob实现类中,添加相应setter方法获取。

    5、Trigger用来告诉Quartz调度程序什么时候执行,常用的触发器有2种:SimpleTrigger(类似于Timer)、CronTrigger(类似于Linux的Crontab)。

    6、实际上,Quartz在进行调度器初始化的时候,会加载quartz.properties文件进行一些属性的设置,比如Quartz后台线程池的属性(threadCount)、作业存储设置等。它会先从工程中找,如果找不到那么就是用quartz.jar中的默认的quartz.properties文件。

    7、Quartz存在监听器的概念,比如任务执行前后、任务的添加等,可以方便实现任务的监控。

    CronTrigger示例

    这里给出一些常用的示例:

    0 15 10 ? * 每天10点15分触发

    0 15 10 ? 2017 2017年每天10点15分触发

    0 14 * ? 每天下午的 2点到2点59分每分触发

    0 0/5 14 ? 每天下午的 2点到2点59分(整点开始,每隔5分触发)

    0 0/5 14,18 ? 每天下午的 2点到2点59分、18点到18点59分(整点开始,每隔5分触发)

    0 0-5 14 ? 每天下午的 2点到2点05分每分触发

    0 15 10 ? * 6L 每月最后一周的星期五的10点15分触发

    0 15 10 ? * 6#3 每月的第三周的星期五开始触发

    我们可以通过一些Cron在线工具非常方便的生成,比如http://www.pppet.net/等。

    Spring和Quartz的整合

    实际上,Quartz和Spring结合是很方便的,无非就是进行一些配置。大概基于2种方式:

    第一,普通的类,普通的方法,直接在配置中指定(MethodInvokingJobDetailFactoryBean)。

    第二,需要继承QuartzJobBean,复写指定方法(executeInternal)即可。

    然后,就是一些触发器、调度器的配置了,这里不再展开介绍了,只要弄懂了原生的Quartz的使用,那么和Spring的结合使用就会很简单。

    展开全文
  • 本文实例讲述了Java任务调度的常见实现方法与比较。分享给大家供大家参考,具体如下:简介: 综观目前的 Web 应用,多数应用都具备任务调度的功能。本文由浅入深介绍了几种任务调度Java 实现方法,包括 Timer,...

    本文实例讲述了Java任务调度的常见实现方法与比较。分享给大家供大家参考,具体如下:

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

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

    Timer

    ScheduledExecutor

    开源工具包 Quartz

    开源工具包 JCronTab

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

    Timer

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

    package com.ibm.scheduler;

    import java.util.Timer;

    import java.util.TimerTask;

    public class TimerTest extends TimerTask {

    private String jobName = "";

    public TimerTest(String jobName) {

    super();

    this.jobName = jobName;

    }

    @Override

    public void run() {

    System.out.println("execute " + jobName);

    }

    public static void main(String[] args) {

    Timer timer = new Timer();

    long delay1 = 1 * 1000;

    long period1 = 1000;

    // 从现在开始 1 秒钟之后,每隔 1 秒钟执行一次 job1

    timer.schedule(new TimerTest("job1"), delay1, period1);

    long delay2 = 2 * 1000;

    long period2 = 2000;

    // 从现在开始 2 秒钟之后,每隔 2 秒钟执行一次 job2

    timer.schedule(new TimerTest("job2"), delay2, period2);

    }

    }

    Output:

    execute job1

    execute job1

    execute job2

    execute job1

    execute job1

    execute job2

    使用 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 com.ibm.scheduler;

    import java.util.concurrent.Executors;

    import java.util.concurrent.ScheduledExecutorService;

    import java.util.concurrent.TimeUnit;

    public class ScheduledExecutorTest implements Runnable {

    private String jobName = "";

    public ScheduledExecutorTest(String jobName) {

    super();

    this.jobName = jobName;

    }

    @Override

    public void run() {

    System.out.println("execute " + jobName);

    }

    public static void main(String[] args) {

    ScheduledExecutorService service = Executors.newScheduledThreadPool(10);

    long initialDelay1 = 1;

    long period1 = 1;

    // 从现在开始1秒钟之后,每隔1秒钟执行一次job1

    service.scheduleAtFixedRate(

    new ScheduledExecutorTest("job1"), initialDelay1,

    period1, TimeUnit.SECONDS);

    long initialDelay2 = 1;

    long delay2 = 1;

    // 从现在开始2秒钟之后,每隔2秒钟执行一次job2

    service.scheduleWithFixedDelay(

    new ScheduledExecutorTest("job2"), initialDelay2,

    delay2, TimeUnit.SECONDS);

    }

    }

    Output:

    execute job2

    execute job1

    execute job2

    execute job1

    execute job2

    execute job1

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

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

    }

    }

    Output:

    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

    清单 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 实现每星期二 16:38 的调度安排:

    package com.ibm.scheduler;

    import java.util.Date;

    import org.quartz.Job;

    import org.quartz.JobDetail;

    import org.quartz.JobExecutionContext;

    import org.quartz.JobExecutionException;

    import org.quartz.Scheduler;

    import org.quartz.SchedulerFactory;

    import org.quartz.Trigger;

    import org.quartz.helpers.TriggerUtils;

    public class QuartzTest implements Job {

    @Override

    //该方法实现需要执行的任务

    public void execute(JobExecutionContext arg0) throws JobExecutionException {

    System.out.println("Generating report - "

    + arg0.getJobDetail().getFullName() + ", type ="

    + arg0.getJobDetail().getJobDataMap().get("type"));

    System.out.println(new Date().toString());

    }

    public static void main(String[] args) {

    try {

    // 创建一个Scheduler

    SchedulerFactory schedFact =

    new org.quartz.impl.StdSchedulerFactory();

    Scheduler sched = schedFact.getScheduler();

    sched.start();

    // 创建一个JobDetail,指明name,groupname,以及具体的Job类名,

    //该Job负责定义需要执行任务

    JobDetail jobDetail = new JobDetail("myJob", "myJobGroup",

    QuartzTest.class);

    jobDetail.getJobDataMap().put("type", "FULL");

    // 创建一个每周触发的Trigger,指明星期几几点几分执行

    Trigger trigger = TriggerUtils.makeWeeklyTrigger(3, 16, 38);

    trigger.setGroup("myTriggerGroup");

    // 从当前时间的下一秒开始执行

    trigger.setStartTime(TriggerUtils.getEvenSecondDate(new Date()));

    // 指明trigger的name

    trigger.setName("myTrigger");

    // 用scheduler将JobDetail与Trigger关联在一起,开始调度任务

    sched.scheduleJob(jobDetail, trigger);

    } catch (Exception e) {

    e.printStackTrace();

    }

    }

    }

    Output:

    Generating report - myJobGroup.myJob, type =FULL

    Tue Feb 8 16:38:00 CST 2011

    Generating report - myJobGroup.myJob, type =FULL

    Tue Feb 15 16:38:00 CST 2011

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

    希望本文所述对大家java程序设计有所帮助。

    展开全文
  • 一、什么是QuartzQuartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,完全由Java开发,可以用来执行定时任务,类似于java.util.Timer。但是相较于Timer, Quartz增加了很多功能:持久性作业 - 就是...
  • 本文由浅入深介绍了几种任务调度Java 实现方法,包括 Timer,Scheduler, Quartz 以及 JCron Tab,并对其优缺点进行比较,目的在于给需要开发任务调度的程序员提供有价值的参考。前言任务调度是指基于给定时间点,...
  • 1、常用的定时调度工具:Timer和Quartz对时间的控制上,能实现简单的定时任务。若特定时间的定时任务,需要用Quartz,它的定时机制更加庞大。Quartz可以使用多个执行线程来实现。二、Timer简介1、Timer的定义以及...
  • Quartz是Java中比较成熟和常用的任务调度器。Spring框架对其提供了集成。Quartz非常容易使用。一个任务调度器最基本的三个元素是Job(or Task):需要定时处理的事情Trigger: 事件触发时间点(一次性的、固定周期性的、...
  • 本文由浅入深介绍四种任务调度Java 实现:TimerScheduledExecutor开源工具包 Quartz开源工具包 JCronTab此外,为结合实现复杂的任务调度,本文还将介绍 Calendar 的一些使用方法。Timer相信大家都已经非常熟悉 ...
  • JAVA常用类库

    2021-02-26 13:30:18
    1 1.StringBuffer23 StringBuffer是使用缓冲区的,本身也是操作字符串的,但是与String类不同,String类的内容一旦声明之后则不可改变,改变的只是其内存地址的指向,而StringBuffer的内容是可以改变的4 对于...
  • 任务调度概述 在企业级应用,经常会制定一些“计划任务” 即在某个时间点做某件事情 核心是以时间为关注点,即在一个特定的时间点,系统执行指定的一个操作 任务调度涉及多线程并发、线程池维护、运行时间规则...
  • Java调度实现方案对比

    2021-02-12 09:08:22
    Timer相信大家都已经非常熟悉 Java.util.Timer 了,它是最简单的一种实现任务调度的方法使用 Timer 实现任务调度的核心类是 Timer 和 TimerTask。其中 Timer 负责设定 TimerTask 的起始与间隔执行时间。使用者只需要...
  • 本文由浅入深介绍四种任务调度Java 实现:TimerScheduledExecutor开源工具包 Quartz开源工具包 JCronTab此外,为结合实现复杂的任务调度,本文还将介绍 Calendar 的一些使用方法。Timer相信大家都已经非常熟悉 ...
  • Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,Quartz 是一个完全由 Java 编写的开源作业调度框架,为在 Java 应用程序进行作业调度提供了简单却强大的机制,可以与 J2EE与 J2SE应用程序相...
  • java定时任务调度工具

    2021-03-05 15:30:52
    二、java中常用的定时任务调度工具:TimerQuartz2.1两者区别:Timer源自jdk,Quartz需要额外引入jar包。Timer功能少,使用方便,能解决许多常见问题。Quartz功能强大,使用麻烦,能解决几乎所有问题。Timer底层通过...
  • java虚拟机会按照特定的机制为程序的每个线程分配CPU的使用权,这种机制被称为线程的调度。在计算机,线程调度有两种模型,分别是分时调度模型和抢占式调度模型。分时调度模型:指让所有的线程轮流获得CPU的使用...
  • java中的定时任务, 使用java实现有3种方式: 1, 使用普通thread实现 @Test public void test1() { // 单位: 毫秒 final long timeInterval = 1000; Runnable runnable = new Runnable() { .
  • 生活的味道睁开眼看一看窗外的阳光,伸一个懒腰,拿起放在床一旁的水白开水,甜甜的味道,晃着尾巴东张西望的猫猫,在窗台上舞蹈。...Quartz 任务调度是什么Quartz 是 OpenSymphony 开源组织在 Job schedul...
  • 常用调度算法

    2021-03-12 21:12:18
    先来先服务(First Come First Served,FCFS)一种最简单的调度算法,在进程采用FCFS算法时,则每次调度是从就绪队列选择一个最先进入该队列的进程,为之分配处理机。最短作业优先(Shortest Job First,SJF)每次从...
  • 本文由浅入深介绍四种任务调度Java 实现:(1)Timer(2)ScheduledExecutor(3)开源工具包 Quartz(4)开源工具包 JCronTab此外,为结合实现复杂的任务调度,本文还将介绍 Calendar 的一些使用方法。Timer相信大家都...
  • 一、线程池1.newCachedThreadPool(1)缓存型池子,先查看池有没有以前建立的线程,如果有,就reuse,如果没有,就建立一个新的线程加入池;(2)缓存型池子,通常用于执行一些生存周期很短的异步型任务;因此一些...
  • 在前面介绍了java的多线程的基本原理信息:《Java线程池架构原理和源码解析》,本文对这个java本身的线程池的调度器做一个简单扩展,如果还没读过上一篇文章,建议读一下,因为这是调度器的核心组件部分。...
  • 2014-02-01JAVA常用类库1.StringBufferStringBuffer是使用缓冲区的,本身也是操作字符串的,但是与String类不同,String类的内容一旦声明之后则不可改变,改变的只是其内存地址的指向,而StringBuffer的内容是可以...
  • 版权声明:本文为openXu原创文章【openXu的博客】,未经博主允许不得以任何形式转载 文章目录1. 相关类介绍1.1 ContinuationInterceptor续体拦截器1.2 ... 调度器实现线程切换的原理3.1 调度器的平台实现3.2 An.

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 101,617
精华内容 40,646
关键字:

java常用调度器

java 订阅