轉自:https://www.cnblogs.com/slei212/p/10732260.html、https://segmentfault.com/a/1190000012506685、https://www.iteye.com/blog/zw7534313-2435135
從實現的技術上來分類,Java定時任務目前主要有三種:
- Java自帶的java.util.Timer類,這個類允許調度一個java.util.TimerTask任務。使用這種方式可以讓你的程序按照某一個頻度執行,但不能在指定時間運行;而且作業類需要集成java.util.TimerTask,一般用的較少。
- Quartz,這是一個功能比較強大的的調度器,可以讓你的程序在指定時間執行,也可以按照某一個頻度執行;使用起來需要繼承org.springframework.scheduling.quartz.QuartzJobBean,配置稍顯複雜,所以,一般會使用spring集成quartz,稍後會詳細介紹;
- Spring3.0以後自帶的task,即:spring schedule,可以將它看成一個輕量級的Quartz,而且使用起來比Quartz簡單許多。
有時項目中既需要異步任務, 也需要調度任務, 想把這兩個異步線程池分來就需要配置兩個線程池。
調度任務添加 @Scheduled 註解, 需要異步執行的方法添加 @Async 註解
中間遇到點小問題, 異步任務線程池總是不生效, 而是使用的調度任務線程池, 經過查文檔不斷嘗試解決了.
公司利用 slf4j 的 MDC 做鏈路跟蹤, 所以還需要添加前置操作, 使用 TaskDecorator 實現。
代碼如下:
AsyncConfig.java
package com.ecej.esmart.autodispatch.config;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.TaskDecorator;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.Map;
import java.util.concurrent.Executor;
@Slf4j
@EnableAsync
@Configuration
public class AsyncConfig implements AsyncConfigurer {
@Bean
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("async-pool-");
executor.setTaskDecorator(new MdcTaskDecorator());
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.initialize();
return executor;
}
class MdcTaskDecorator implements TaskDecorator {
@Override
public Runnable decorate(Runnable runnable) {
Map<String, String> contextMap = MDC.getCopyOfContextMap();
try {
if (contextMap != null) {
MDC.setContextMap(contextMap);
}
runnable.run();
} finally {
/** 清理後會導致父線程的上下文清空,進入時會複製父線程的內容進行覆蓋,可不清理 */
//MDC.clear();
}
return runnable;
}
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return (throwable, method, params) -> {
log.error("異步任務異常:方法:{} 參數:{}", method.getName(), JSON.toJSONString(params));
log.error(throwable.getMessage(), throwable);
};
}
}
SchedulingConfig.java
package com.ecej.esmart.autodispatch.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
@Slf4j
@Configuration
@EnableScheduling
public class SchedulingConfig implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
scheduledTaskRegistrar.setTaskScheduler(taskScheduler());
}
@Bean(destroyMethod = "shutdown")
public ThreadPoolTaskScheduler taskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(5);
scheduler.setThreadNamePrefix("dispatch-");
scheduler.setAwaitTerminationSeconds(600);
scheduler.setErrorHandler(throwable -> log.error("調度任務發生異常", throwable));
scheduler.setWaitForTasksToCompleteOnShutdown(true);
return scheduler;
}
}
添加定時任務:
①串行方式:
- 在啓動類上加註解:@EnableScheduling即可實現。
- 任務添加@Scheduled註解:@Scheduled接受兩種定時的設置,一種是cornexpression,一種是Rate/Delay表達式
②並行方式:
爲了提高任務執行效率,可以採用並行方式執行定時任務,任務之間互不影響,只要實現SchedulingConfigurer接口就可以。
@Configuration
public class ScheduledConfig implements SchedulingConfigurer {
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setScheduler(setExecutor());
}
@Bean(destroyMethod="shutdown")
public Executor setExecutor(){
return Executors.newScheduledThreadPool(5); // 5個線程來處理。
}
}
SpringBoot 定時任務@Scheduled及SchedulingConfigurer
注:Spring 中,創建定時任務除了使用@Scheduled 註解外,還可以使用 SchedulingConfigurer。
@Schedule 註解有一個缺點,其定時的時間不能動態的改變,而基於 SchedulingConfigurer 接口的方式可以做到。SchedulingConfigurer 接口可以實現在@Configuration 類上,同時不要忘了,還需要@EnableScheduling 註解的支持。
1.基於註解實現方式
@Component
@EnableScheduling //開啓定時任務
@EnableAsync //開啓多線程
public class TimerJob {
private Logger log = Logger.getLogger(TimerJob.class);
@Async //異步方法;這些方法將在執行的時候,將會在獨立的線程中被執行,調用者無需等待它的完成,即可繼續其他的操作。
@Scheduled(cron = "* 0/30 * * * ?") //時間規則
public void matterTasks() {
//業務邏輯
}
}
2.SchedulingConfigurer實現方式
2.1、在啓動類必須加上@EnableScheduling //開啓定時任務
2.2、實現SchedulingConfigurer並重寫configureTasks方法
@Component //實現SchedulingConfigurer並重寫configureTasks方法
public class OrderJobThread implements SchedulingConfigurer {
private Logger log = LoggerFactory.getLogger(OrderJobThread.class);
private String cron = "* 0/1 * * * ?"; //調用set方法可動態設置時間規則
public String getCron() {
return cron;
}
public void setCron(String cron) {
this.cron = cron;
}
private String name = "測試"; //調用set方法可以動態設置日誌名
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.addCronTask(new Runnable() {
@Override
public void run() {
log.warn(name+" --- > 開始");
//業務邏輯
log.warn(name+" --- > 結束");
}
}, cron); //加入時間
}
}
2.3、設置項目啓動後,初始化 定時任務執行時間
有時希望項目在啓動的時候加載一些系統參數或者方法,就要用到ApplicationRunner
ApplicationRunner是一個接口,我們需要實現它,並重寫run()方法,當項目啓動時,run()方法便會自動執行
@Component
@Order(value = 1) //value值會 從小至大的執行
public class TimmerStartController implements ApplicationRunner {
private static Logger logger = LoggerFactory.getLogger(TimmerStartController.class);
@Autowired
private OrderJobThread orderJobThread; //得到定時任務
@Override
public void run(ApplicationArguments args) throws Exception {
logger.info("=========== 項目啓動後,初始化 定時任務執行時間 =============");
orderJobThread.setCron("* 0/5 * * * ?"); //根據需求重新賦值時間規則
orderJobThread.setName("ordersTasks"); //賦值name
}
}