场景:项目启动时初始化循环定时,在一定的时间任务间隔内执行任务,如果用户在客户端更新了配置,那么循环定时需要重新执行。
分析:系统初始化给个方法运行起来,再写个controller通过页面调用后刷新定时即可。由于是固定的任务时间间隔,因此使用了scheduleAtFixedRate()实现效果,使用过ExecutorService的知道其中有以下几种方法:
void shutdown();
List<Runnable> shutdownNow();
boolean isShutdown();
boolean isTerminated();
boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException;
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
......
其中比较关心的就是如果请求过来,查找数据库定时数据后,怎么刷新当前任务调度服务,很明显,shutdown();shutdownNow();isShutdown()不就是我们需要的吗?
经过实验发现,调用shutdown()后,再次scheduleAtFixedRate(new Runable(), 0, timeBucket, TimeUnit.SECONDS)就会出问题,原因是shutdown()后,这个任务调度器就不再起作用了,就是说已经不存在了,源码中有说明:
if a security manager exists and shutting down this ExecutorService may manipulate threads that the caller is not permitted to modify because it does not hold.
这就出现了一个问题,如果我shutdown调度器后,我再创建一个周期操作将会不起作用,抛出异常,如下:
ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
service.scheduleAtFixedRate(task, 0, timeBucket, TimeUnit.SECONDS);
service.shutdown();
service.scheduleAtFixedRate(task, 0, timeBucket, TimeUnit.SECONDS);
第一次创建周期任务执行后,调用shutdown()方法,接着再创建一个周期任务, 这样将会抛出异常。
解决方法:
public class Task {
private static ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
public void ApiSendJob(Integer flag) {
//查询数据库,获取执行频次timeBucket
String timeBucket= dataSource.getObject();
//......
long time = Long.parseLong(timeBucket.getConfigExtend());
log.info("执行API接口定时任务,频次{}秒......", timeBucket);
Runnable task = new Runnable() {
@Override
public void run() {
try {
//TODO 查询定时,执行计划
log.info("API Task 正在执行......");
} catch (Exception e) {
e.printStackTrace();
}
}
};
//此处传1,说明项目启动后会进入此分支创建周期任务
if (flag == 1) {
service.scheduleAtFixedRate(task, 0, time , TimeUnit.SECONDS);
} else {
//销毁之前创建的周期任务
service.shutdown();
//重新创建ScheduledExecutorService
service = Executors.newSingleThreadScheduledExecutor();
//创建新的周期任务
service.scheduleAtFixedRate(task, 0, time , TimeUnit.SECONDS);
}
}
}
修改后测试:
初始化5秒/次
修改后10秒/次
关键点就是销毁后需要重新创建Executors.newSingleThreadScheduledExecutor()。当然,想要更严谨一些的业务逻辑,可以判断ScheduledFuture对象所有任务是否执行完毕,全部执行完毕后再shutdown(),如果业务是常态化的间隔执行,相对就没有那么高要求了。