在本文中,我们将介绍与 Java 调度程序相关的以下主题:
- 在 Java 中调度任务
SchedularConfigurer
对比@Scheduled
- 动态更改
cron
表达式 - 两个任务之间的依赖执行
在 Java 中调度任务
调度器用于调度一个线程或任务,该线程或任务在某个时间段或以固定的时间间隔周期性地执行。有多种方法可以在 Java 中安排任务。
-
java.util.TimerTask
-
java.util.concurrent.ScheduledExecutorService
- Quartz Scheduler
-
org.springframework.scheduling.TaskScheduler
TimerTask
由demon线程执行。任务中的任何延迟都会延迟计划中的其他任务。因此,当多个任务需要在某个时间异步执行时,这不是一个可行的选择。
让我们看一个例子:
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
现在,我们将尝试编写一个调度器任务,我们希望在其中异步执行 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 都由使用 ThreadPoolTaskScheduler 创建的线程池处理
动态更改 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 来控制池中的线程。