Java 调度程序

在本文中,我们将介绍与 Java 调度程序相关的以下主题:

  • 在 Java 中调度任务
  • SchedularConfigurer 对比@Scheduled
  • 动态更改 cron 表达式
  • 两个任务之间的依赖执行

在 Java 中调度任务

调度器用于调度一个线程或任务,该线程或任务在某个时间段或以固定的时间间隔周期性地执行。有多种方法可以在 Java 中安排任务。

  •  java.util.TimerTask 
  •  java.util.concurrent.ScheduledExecutorService 
  •  Quartz Scheduler
  •  org.springframework.scheduling.TaskScheduler

TimerTaskdemon线程执行。任务中的任何延迟都会延迟计划中的其他任务。因此,当多个任务需要在某个时间异步执行时,这不是一个可行的选择。

让我们看一个例子:

package com.example.timerExamples;

import java.util.Timer;

public class ExecuteTimer {

    public static void main(String[] args) {
        TimerExample te1 = new TimerExample("Task1");
        TimerExample te2 = new TimerExample("Task2");

        Timer t = new Timer();
        t.scheduleAtFixedRate(te1, 0, 5 * 1000);
        t.scheduleAtFixedRate(te2, 0, 1000);
    }
}
package com.example.timerExamples;

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

public class TimerExample extends TimerTask {

    private String name;

    public TimerExample(String n) {
        this.name = n;
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " " + name + " the task has executed successfully " + new Date());
        if ("Task1".equalsIgnoreCase(name)) {
            try {
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }

    }
}

输出:

Timer-0  Task1 the task has executed successfully Wed Nov 14 14:32:49 GMT 2018
Timer-0  Task2 the task has executed successfully Wed Nov 14 14:32:59 GMT 2018
Timer-0  Task2 the task has executed successfully Wed Nov 14 14:32:59 GMT 2018
Timer-0  Task2 the task has executed successfully Wed Nov 14 14:32:59 GMT 2018
Timer-0  Task2 the task has executed successfully Wed Nov 14 14:32:59 GMT 2018
Timer-0  Task2 the task has executed successfully Wed Nov 14 14:32:59 GMT 2018
Timer-0  Task2 the task has executed successfully Wed Nov 14 14:32:59 GMT 2018
Timer-0  Task1 the task has executed successfully Wed Nov 14 14:32:59 GMT 2018
Timer-0  Task2 the task has executed successfully Wed Nov 14 14:33:09 GMT 2018
Timer-0  Task2 the task has executed successfully Wed Nov 14 14:33:09 GMT 2018
Timer-0  Task2 the task has executed successfully Wed Nov 14 14:33:09 GMT 2018
Timer-0  Task2 the task has executed successfully Wed Nov 14 14:33:09 GMT 2018
Timer-0  Task2 the task has executed successfully Wed Nov 14 14:33:09 GMT 2018
Timer-0  Task1 the task has executed successfully Wed Nov 14 14:33:09 GMT 2018

在上面的执行中,很明显任务 2 被卡住了,因为正在处理任务 1 的线程将要休眠 10 秒。因此,只有一个恶魔线程同时在任务 1 和任务 2 上工作,如果其中一个被击中,所有任务都将被推回。

ScheduledExecutorService 并 TaskScheduler 以相同的方式工作。与前者的唯一区别是Java库,后者是Spring框架。因此,如果应用程序在 Spring 中,那么 TaskScheduler 调度作业可能是一个更好的选择。

现在,让我们看看 TaskScheduler 接口的用法,我们可以在 Spring 中使用它。

SchedularConfigurer 与@Scheduled

Spring 在@Scheduled.

线程由 Spring 框架处理,我们无法控制执行任务的线程。让我们看一下下面的例子:

@Configuration
@EnableScheduling
public class ScheduledConfiguration {

    @Scheduled(fixedRate = 5000)
    public void executeTask1() {
        System.out.println(Thread.currentThread().getName()+" The Task1 executed at "+ new Date());
        try {
            Thread.sleep(10000);
          } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    @Scheduled(fixedRate = 1000)
    public void executeTask2() {
        System.out.println(Thread.currentThread().getName()+" The Task2 executed at "+ new Date());
    }
}

输出:

scheduling-1 The Task2 executed at Wed Nov 14 14:22:59 GMT 2018
scheduling-1 The Task2 executed at Wed Nov 14 14:22:59 GMT 2018
scheduling-1 The Task2 executed at Wed Nov 14 14:22:59 GMT 2018
scheduling-1 The Task2 executed at Wed Nov 14 14:22:59 GMT 2018
scheduling-1 The Task2 executed at Wed Nov 14 14:22:59 GMT 2018
scheduling-1 The Task1 executed at Wed Nov 14 14:22:59 GMT 2018
scheduling-1 The Task2 executed at Wed Nov 14 14:23:09 GMT 2018
scheduling-1 The Task2 executed at Wed Nov 14 14:23:09 GMT 2018
scheduling-1 The Task2 executed at Wed Nov 14 14:23:09 GMT 2018
scheduling-1 The Task2 executed at Wed Nov 14 14:23:09 GMT 2018
scheduling-1 The Task2 executed at Wed Nov 14 14:23:09 GMT 2018
scheduling-1 The Task1 executed at Wed Nov 14 14:23:09 GMT 2018
有一个线程 scheduling-1,它同时处理 task1 和 task2。当 task1 进入睡眠状态 10 秒时,task 2 也在等待它。因此,如果有两个作业同时运行,一个会等待另一个完成。

现在,我们将尝试编写一个调度器任务,我们希望在其中异步执行 task1 和 task2。将有一个线程池,我们将在 ThreadPoolTaskScheduler. 该类需要实现 SchedulingConfigurer 接口。与  @Scheduled.

@Configuration
@EnableScheduling
public class ScheduledConfiguration implements SchedulingConfigurer {

        TaskScheduler taskScheduler;
        private ScheduledFuture<?> job1;
        private ScheduledFuture<?> job2;
        @Override
        public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {

            ThreadPoolTaskScheduler threadPoolTaskScheduler =new ThreadPoolTaskScheduler();
            threadPoolTaskScheduler.setPoolSize(10);// Set the pool of threads
            threadPoolTaskScheduler.setThreadNamePrefix("scheduler-thread");
            threadPoolTaskScheduler.initialize();
            job1(threadPoolTaskScheduler);// Assign the job1 to the scheduler
            job2(threadPoolTaskScheduler);// Assign the job1 to the scheduler
            this.taskScheduler=threadPoolTaskScheduler;// this will be used in later part of the article during refreshing the cron expression dynamically
            taskRegistrar.setTaskScheduler(threadPoolTaskScheduler);

        }

        private void job1(TaskScheduler scheduler) {
               job1 = scheduler.schedule(new Runnable() {
               @Override
               public void run() {
                  System.out.println(Thread.currentThread().getName() + " The Task1 executed at " + new Date());
                    try {
                    Thread.sleep(10000);
                    } catch (InterruptedException e) {
                      // TODO Auto-generated catch block
                      e.printStackTrace();
                  }
                  }
               }, new Trigger() {
                    @Override
                    public Date nextExecutionTime(TriggerContext triggerContext) {
                     String cronExp = "0/5 * * * * ?";// Can be pulled from a db .
                     return new CronTrigger(cronExp).nextExecutionTime(triggerContext);
                  }
                });
           }

        private void job2(TaskScheduler scheduler){
                   job2=scheduler.schedule(new Runnable(){
                   @Override
                   public void run() {
                     System.out.println(Thread.currentThread().getName()+" The Task2 executed at "+ new Date());
                      }
                     }, new Trigger(){
                        @Override
                        public Date nextExecutionTime(TriggerContext triggerContext) {
                         String cronExp="0/1 * * * * ?";//Can be pulled from a db . This will run every minute
                         return new CronTrigger(cronExp).nextExecutionTime(triggerContext);
                     }
                  });
        }
   }
输出:
scheduler-thread1 The Task2 executed at Wed Nov 14 15:02:46 GMT 2018
scheduler-thread2 The Task2 executed at Wed Nov 14 15:02:47 GMT 2018
scheduler-thread3 The Task2 executed at Wed Nov 14 15:02:48 GMT 2018
scheduler-thread2 The Task2 executed at Wed Nov 14 15:02:49 GMT 2018
scheduler-thread1 The Task2 executed at Wed Nov 14 15:02:50 GMT 2018
scheduler-thread7 The Task1 executed at Wed Nov 14 15:02:50 GMT 2018
scheduler-thread3 The Task2 executed at Wed Nov 14 15:02:51 GMT 2018
scheduler-thread5 The Task2 executed at Wed Nov 14 15:02:52 GMT 2018
scheduler-thread2 The Task2 executed at Wed Nov 14 15:02:53 GMT 2018
scheduler-thread6 The Task2 executed at Wed Nov 14 15:02:54 GMT 2018
scheduler-thread6 The Task2 executed at Wed Nov 14 15:02:55 GMT 2018
scheduler-thread6 The Task2 executed at Wed Nov 14 15:02:56 GMT 2018
scheduler-thread6 The Task2 executed at Wed Nov 14 15:02:57 GMT 2018
scheduler-thread6 The Task2 executed at Wed Nov 14 15:02:58 GMT 2018
scheduler-thread6 The Task2 executed at Wed Nov 14 15:02:59 GMT 2018
scheduler-thread6 The Task2 executed at Wed Nov 14 15:03:00 GMT 2018
scheduler-thread2 The Task2 executed at Wed Nov 14 15:03:01 GMT 2018
scheduler-thread2 The Task2 executed at Wed Nov 14 15:03:02 GMT 2018
scheduler-thread2 The Task2 executed at Wed Nov 14 15:03:03 GMT 2018
scheduler-thread2 The Task2 executed at Wed Nov 14 15:03:04 GMT 2018
scheduler-thread10 The Task2 executed at Wed Nov 14 15:03:05 GMT 2018
scheduler-thread8 The Task1 executed at Wed Nov 14 15:03:05 GMT 2018-

我正在创建两个工作:job1 和 job2。然后,我将使用 TaskScheduler. 这一次,我使用 Cron 表达式每隔五秒安排一次 job1,每秒安排一次 job2。Job1 卡住了 10 秒,我们会看到 job2 仍然流畅运行,没有中断。我们看到 task1 和 task 2 都由使用 ThreadPoolTask​​Scheduler 创建的线程池处理

动态更改 Cron 表达式

我们总是可以使用 Spring 配置将 cron 表达式保存在属性文件中。如果 Spring Config 服务器不可用,我们也可以从数据库中获取它。cron 表达式的任何更新都会更新调度程序。但是为了取消当前的调度并执行新的调度,我们可以暴露一个 API 来刷新 cron 作业:

public void refreshCronSchedule(){

  if(job1!=null){
   job1.cancel(true);
   scheduleJob1(taskScheduler);
  }

  if(job2!=null){
   job2.cancel(true);
   scheduleJob2(taskScheduler);
  }
}

此外,您可以从任何控制器调用该方法来刷新 cron 计划。

两个任务之间的依赖执行

到目前为止,我们知道我们可以使用 TaskScheduler and Schedulingconfigurer 接口异步执行作业。现在,假设我们有在凌晨 1 点运行一小时的作业 1 和在凌晨 2 点运行的作业 2。但是,除非 job1 完成,否则 job2 不应启动。我们还有另一个可以在凌晨 1 点到 2 点之间运行并且独立于其他作业的作业列表。

让我们看看如何在 job1 和 job2 之间创建依赖关系,同时在预定时间异步运行所有作业。

首先,让我们声明一个 volatile 变量:

private volatile boolean job1Flag=false;
 private void scheduleJob1(TaskScheduler scheduler) {
           job1 = scheduler.schedule(new Runnable() {
             @Override
             public void run() {           
                  System.out.println(Thread.currentThread().getName() + " The Task1 executed at " + new Date());
                  try {
                   Thread.sleep(10000);
                  } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                  }
                  job1Flag=true;// setting the flag true to mark it complete
           }
           }, new Trigger() {
              @Override
              public Date nextExecutionTime(TriggerContext triggerContext) {
                     String cronExp = "0/5 * * * * ?";// Can be pulled from a db 
                     return new CronTrigger(cronExp).nextExecutionTime(triggerContext);
                   }
               });

          }
private void scheduleJob2(TaskScheduler scheduler) {
      job2=scheduler.schedule(new Runnable(){

       @Override
       public void run() {
         synchronized(this){
           while(!job1Flag){
               System.out.println(Thread.currentThread().getName()+" waiting for job1 to complete to execute "+ new Date());
             try {
                  wait(1000);// add any number of seconds to wait 
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                  }
           }
         }

          System.out.println(Thread.currentThread().getName()+" The Task2 executed at "+ new Date());
          job1Flag=false;
      }
     }, new Trigger(){
            @Override
            public Date nextExecutionTime(TriggerContext triggerContext) {
            String cronExp="0/5 * * * * ?";//Can be pulled from a db . This will run every minute
            return new CronTrigger(cronExp).nextExecutionTime(triggerContext);
      }
    });
  }
scheduler-thread2 The Task1 executed at Wed Nov 14 16:30:50 GMT 2018
scheduler-thread1 waiting for job1 to complete to execute Wed Nov 14 16:30:51 GMT 2018
scheduler-thread1 waiting for job1 to complete to execute Wed Nov 14 16:30:52 GMT 2018
scheduler-thread1 waiting for job1 to complete to execute Wed Nov 14 16:30:53 GMT 2018
scheduler-thread1 waiting for job1 to complete to execute Wed Nov 14 16:30:54 GMT 2018
scheduler-thread1 waiting for job1 to complete to execute Wed Nov 14 16:30:55 GMT 2018
scheduler-thread1 waiting for job1 to complete to execute Wed Nov 14 16:30:56 GMT 2018
scheduler-thread1 waiting for job1 to complete to execute Wed Nov 14 16:30:57 GMT 2018
scheduler-thread1 waiting for job1 to complete to execute Wed Nov 14 16:30:58 GMT 2018
scheduler-thread1 waiting for job1 to complete to execute Wed Nov 14 16:30:59 GMT 2018
scheduler-thread1 The Task2 executed at Wed Nov 14 16:31:00 GMT 2018
scheduler-thread2 The Task1 executed at Wed Nov 14 16:31:05 GMT 2018
scheduler-thread3 waiting for job1 to complete to execute Wed Nov 14 16:31:05 GMT 2018
scheduler-thread3 waiting for job1 to complete to execute Wed Nov 14 16:31:06 GMT 2018
scheduler-thread3 waiting for job1 to complete to execute Wed Nov 14 16:31:07 GMT 2018
scheduler-thread3 waiting for job1 to complete to execute Wed Nov 14 16:31:08 GMT 2018
scheduler-thread3 waiting for job1 to complete to execute Wed Nov 14 16:31:09 GMT 2018
scheduler-thread3 waiting for job1 to complete to execute Wed Nov 14 16:31:10 GMT 2018
scheduler-thread3 waiting for job1 to complete to execute Wed Nov 14 16:31:11 GMT 2018
scheduler-thread3 waiting for job1 to complete to execute Wed Nov 14 16:31:12 GMT 2018
scheduler-thread3 waiting for job1 to complete to execute Wed Nov 14 16:31:13 GMT 2018
scheduler-thread3 waiting for job1 to complete to execute Wed Nov 14 16:31:14 GMT 2018
scheduler-thread3 The Task2 executed at Wed Nov 14 16:31:15 GMT 2018
scheduler-thread1 The Task1 executed at Wed Nov 14 16:31:20 GMT 2018

我们选择使用 volatile 布尔标志,这样它就不会缓存在线程本地,而是保存在主内存中,并且可以被池中的所有线程使用。根据该标志,job2 会无限期地等待,直到 job1 完成。现在,如果 job1 挂起,job2 可能会无限期地等待。

结论

在 Java 中有多种调度方式,现在,我们知道如何使用调度器 API 和 Spring 调度器 API 来控制池中的线程。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值