1、場景引入java
前不久作過一個根據下載指令定時下載文件到服務器的需求。輪詢下載的週期須要根據下載任務量的大小動態修改,下載任務密集的時候就週期縮小,下載任務少許時就擴大週期時間。java自己和第三方開源框架Spring共有三種執行定時任務的方式:spring
1) Java自帶的java.util.Timer類:這個類容許你調度一個java.util.TimerTask任務。(這種方式比較古老,自從第三方開源框架出現以來,基本不使用java自帶的定時任務組件)
2) 開源的第三方框架: Quartz 或者 elastic-job , 可是這個比較複雜和重量級,適用於分佈式場景下的定時任務,能夠根據須要多實例部署定時任務。
3) 使用Spring提供的註解: @Schedule 。 若是定時任務執行時間較短,而且比較單一,可使用這個註解。
緩存
以上三種執行定時任務的方式都只能固定執行週期,一旦定時任務跑起來以後就不可能修改週期,只能修改週期後從新啓動,如今須要動態修改執行週期,故這三種方式都不能使用。服務器
2、解決方式框架
既然不能用經過傳統的方式,那就要想到強大的第三方開源框架帶來的便利,Spring從3.0版本開始在框架中加入了一個新的定時任務線程池配置類,即: org.springframework.scheduling.concurrent包中有一個分佈式
ThreadPoolTaskScheduler類,它繼承了抽象類 ExecutorConfigurationSupport 並實現了 AsyncListenableTaskExecutor, SchedulingTaskExecutor, TaskScheduler三個接口 ,其中實現了
TaskScheduler中的一個方法:ScheduledFuture<?> schedule(Runnable task, Trigger trigger)
源碼:
@Override
public ScheduledFuture<?> schedule(Runnable task, Trigger trigger) {
ScheduledExecutorService executor = getScheduledExecutor();
try {
ErrorHandler errorHandler =
(this.errorHandler != null ? this.errorHandler : TaskUtils.getDefaultErrorHandler(true));
return new ReschedulingRunnable(task, trigger, executor, errorHandler).schedule();
}
catch (RejectedExecutionException ex) {
throw new TaskRejectedException("Executor [" + executor + "] did not accept task: " + task, ex);
}
}
經過源碼便可發現只要將對應的定時任務的線程以及包含cron表達式的 Trigger 參數傳入便可按指定的週期啓動定時任務。經過源碼也能夠發現它的線程池大小默認是1:
// ScheduledThreadPoolExecutor.setRemoveOnCancelPolicy(boolean) only available on JDK 7+
private static final boolean setRemoveOnCancelPolicyAvailable =
ClassUtils.hasMethod(ScheduledThreadPoolExecutor.class, "setRemoveOnCancelPolicy", boolean.class);
private volatile int poolSize = 1;
private volatile boolean removeOnCancelPolicy = false;
private volatile ErrorHandler errorHandler;
private volatile ScheduledExecutorService scheduledExecutor;
也就是說定時任務默認是單線程串行執行,若是同時須要執行多個定時任務的話,須要對線程池的大小進行配置,我配置的是線程大小是20,以下:
@Bean(autowire = Autowire.BY_NAME, value = "threadPoolConfigBean")
public ThreadPoolTaskScheduler taskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(20);
return scheduler;
}
而後編寫定時任務接口並實現:
public interface Task {
/**
* 開始任務
*/
String startTask();
/**
* 中止任務
*/
void stopCron();
/**
* 從新觸發任務
*
* @param cron
* @return
*/
boolean triggerAgain(String cron);
}
實現類:
public class CacheInfoDownloadTask implements Task {
Environment env = SpringUtils.getBean(Environment.class);
// spring上下文取bean
private ThreadPoolTaskScheduler threadPoolTaskScheduler
= (ThreadPoolTaskScheduler) SpringUtils.getBean("threadPoolConfigBean");
private CacheInfoDownloadBll cacheInfoDownloadBll
= SpringUtils.getBean(CacheInfoDownloadBll.class);
private ScheduledFuture<?> future;
private static CacheInfoDownloadTask instance;
// 經過單例向外提供惟一實例
public static synchronized CacheInfoDownloadTask getInstance() {
if (null == instance) {
instance = new CacheInfoDownloadTask();
}
return instance;
}
@Override
public String startTask() {
String taskCorn = env.getProperty("cacheInfoDownload.schedule");
future = threadPoolTaskScheduler.schedule(new CacheInfoDownloadJob(), new CronTrigger(taskCorn));
log.info("緩存信息拉取開始...執行週期:{}", taskCorn);
return "ok";
}
@Override
public void stopCron() {
if (future != null) {
future.cancel(true);
}
log.info("緩存信息拉取任務中止成功...");
}
@Override
public boolean triggerAgain(String cron) {
this.stopCron();
future = this.threadPoolTaskScheduler.schedule(new CacheInfoDownloadJob(), new CronTrigger(cron));
log.info("緩存信息拉取任務更改cron表達式後再次觸發成功...執行週期:{}", cron);
return true;
}
private class CacheInfoDownloadJob implements Runnable {
@Override
public void run() {
String schoolId = env.getProperty("school.id");
boolean result = cacheInfoDownloadBll.getCacheInfo(schoolId);
if (result)
log.info("緩存信息拉取並下載任務執行成功...");
else
log.error("緩存信息拉取後存儲失敗...");
}
}
}
定時任務編寫成功後,使用靜態工廠模式在Spring任務啓動同時啓動定時任務,在修改定時任務的執行週期時,只需把調用triggerAgain(String cron)方法便可修改:
@Component
@Order(value = 1)
public class TaskTriggerOrderConfig implements CommandLineRunner {
@Override
public void run(String... strings) throws Exception {
TaskFactory.getInstance(DOWNLOAD_TASK).startTask();
}
}