前言
假设有这么一个场景,我的定时任务执行频率是每2s执行一次,假如正在执行的是一个很重要的任务,可能这个任务需要10s中才能执行完成,那么在内存中可能在某一个时刻已经有很多任务在delay执行了,最可能的和最麻烦的是有一个正在执行,我此时又想重启服务,那么有没有办法监控到是否有任务正在执行呢,是否有办法优雅的关闭正在执行的任务呢,所谓优雅关闭,是指已经正在执行的任务等它执行完成再执行,至于等待的任务,可以关闭。
思路
在上一篇中,我引入了官方的一些文档,其中有一段很有意思
By default, will be searching for an associated scheduler definition:
either a unique TaskScheduler bean in the context, or a TaskScheduler
bean named "taskScheduler" otherwise; the same lookup will also be
performed for a ScheduledExecutorService bean. If neither of the two
is resolvable,a local single-threaded default scheduler will be created
and used within the registrar.
于是我就看看TaskScheduler
这个东东吧,
最后一行的描述是重点,默认用的是ThreadPoolTaskScheduler
,继续往下看
构造方法
一些成员方法
看了这些方法的注视之后就应该知道我们应该最常使用的是下面的四个方法
ScheduledFuture<?> scheduleAtFixedRate(Runnable task, Date startTime, long period)
//Schedule the given Runnable, invoking it at the specified execution time and subsequently with the given period.
ScheduledFuture<?> scheduleAtFixedRate(Runnable task, long period)
//Schedule the given Runnable, starting as soon as possible and invoking it with the given period.
ScheduledFuture<?> scheduleWithFixedDelay(Runnable task, Date startTime, long delay)
//Schedule the given Runnable, invoking it at the specified execution time and subsequently with the given delay between the completion of one execution and the start of the next.
ScheduledFuture<?> scheduleWithFixedDelay(Runnable task, long delay)
//Schedule the given Runnable, starting as soon as possible and invoking it with the given delay between the completion of one execution and the start of the next.
他们的返回值都是ScheduledFuture
,一般情况下,我们只要把任务调度了就可以按照频率策略来执行了,但是,我们这里关心的是如何把这些任务给关掉,那么继续点进去向下看。
发现有个cancel
方法,似乎距离目标越来越近了。继续点进去看
boolean cancel(boolean mayInterruptIfRunning)
//Attempts to cancel execution of this task. This attempt will fail if the task has already completed, has already been cancelled, or could not be cancelled for some other reason. If successful, and this task has not started when cancel is called, this task should never run. If the task has already started, then the mayInterruptIfRunning parameter determines whether the thread executing this task should be interrupted in an attempt to stop the task.
//After this method returns, subsequent calls to isDone() will always return true. Subsequent calls to isCancelled() will always return true if this method returned true.
奇迹出现了,当我们调用这个方法的时候,可以加个参数false,来说明要把正在执行的任务给执行完成才停止这个任务。
代码部分
package cn.juhe.task;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.ScheduledFuture;
@RestController
@Component
@Slf4j
public class TdynamicTaskController {
private static final int threadNums = 10;
@Autowired
private ThreadPoolTaskScheduler threadPoolTaskScheduler;
private final List<ScheduledFuture<?>> futureList = new ArrayList<>();
@Bean
public ThreadPoolTaskScheduler threadPoolTaskScheduler() {
ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
threadPoolTaskScheduler.setPoolSize(threadNums);
threadPoolTaskScheduler.setThreadNamePrefix("taskExecutor-");
//用来设置线程池关闭的时候等待所有任务都完成再继续销毁其他的Bean,这样这些异步任务的销毁就会先于Redis线程池的销毁
threadPoolTaskScheduler.setWaitForTasksToCompleteOnShutdown(true);
//该方法用来设置线程池中任务的等待时间,如果超过这个时候还没有销毁就强制销毁,以确保应用最后能够被关闭,而不是阻塞住
threadPoolTaskScheduler.setAwaitTerminationSeconds(60);
return threadPoolTaskScheduler;
}
@RequestMapping("/startCron")
public String startCron() {
//scheduleAtFixedRate:是以上一个任务开始的时间计时,N秒过去后,检测上一个任务是否执行完毕,如果上一个任务执行完毕,则当前任务立即执行,
// 如果上一个任务没有执行完毕,则需要等上一个任务执行完毕后立即执行
// scheduleWithFixedDelay,是以上一个任务结束时开始计时,120秒过去后,立即执行。
for (int i = 0; i < threadNums; i++) {
//if (futureList.size() <= 3) {
log.info("-------------------------------新建了一个任务-----------------");
futureList.add(threadPoolTaskScheduler.scheduleAtFixedRate(new TestRunable(), 100));
//}
}
log.info("任务开启");
return "startCron";
}
@RequestMapping("/stopCron")
public String stopCron() {
for (ScheduledFuture future : futureList) {
if (future != null) {
future.cancel(false);//false:如果此任务正在执行,则执行完在关闭,true:如果正在执行,也给关闭
}
}
System.out.println("DynamicTask.stopCron()");
return "stopCron";
}
private static class TestRunable implements Runnable {
@Override
@Async
public void run() {
String name = Thread.currentThread().getName();
System.out.println(name+"---执行时间:" + LocalDateTime.now());
Random random = new Random();
int i = random.nextInt(10);
try {
Thread.sleep(i * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name+"---本次执行完成"+ LocalDateTime.now());
}
}
}
结语
官方文档真香,虽然spring的文档很烂,但是有时候为了彻底明白一些问题,还是要去看一些源码和文档。