1. 首先在springboot啓動類上添加 @EnableScheduling 註解。java
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.scheduling.annotation.EnableScheduling; @EnableScheduling @SpringBootApplication public class KittyApiApplication { public static void main(String[] args) { SpringApplication.run(KittyApiApplication.class, args); } }
2. 將任務類註冊到spring容器,而後在其任務方法上添加 @Scheduled 註解,@Scheduled有多個屬性:linux
① cron:cron表達式,指定任務在特定時間執行;web
② fixedDelay:表示上一次任務執行完成後多久再次執行,參數類型爲long,單位ms;spring
③ fixedDelayString:與fixedDelay含義同樣,只是參數類型變爲String;apache
④ fixedRate:表示按必定的頻率執行任務,參數類型爲long,單位ms;centos
⑤ fixedRateString: 與fixedRate的含義同樣,只是將參數類型變爲String;api
⑥ initialDelay:表示延遲多久後第一次執行任務,參數類型爲long,單位ms;tomcat
⑦ initialDelayString:與initialDelay的含義同樣,只是將參數類型變爲String;springboot
⑧ zone:時區,默認爲當前時區,通常沒有用到。多線程
import lombok.extern.slf4j.Slf4j; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; /** * @author * @version 2019/4/15 下午 05:00 */ @Slf4j @Service public class TimedTaskServiceImpl { @Scheduled(cron = "*/10 * * * * ?") public void task01() { log.info("task01: " + System.currentTimeMillis()); } @Scheduled(cron = "*/10 * * * * ?") public void task02() { log.info("task02: " + System.currentTimeMillis()); } @Scheduled(cron = "*/10 * * * * ?") public void task03() { log.info("task03: " + System.currentTimeMillis()); } }
3. springboot 2.0 使用的是spring framework 5.0,在spring 3.0 中就引入了TaskScheduler
接口進行異步執行和任務調度的抽象,spring默認是以單線程執行任務調度,如下展現如何設置多線程在2.1及之前的版本。
(1)springboot 2.1.*之後能夠直接經過在properties中經過屬性配置:
# 線程池大小 spring.task.scheduling.pool.size=10 # 線程名前綴 spring.task.scheduling.thread-name-prefix=task-pool-
(2)springboot 2.0及之前就須要實現 SchedulingConfigurer
接口,點到@EnableScheduling註解中,能夠看到有SchedulingConfigurer接口、SchedulingTaskRegistrar類和ScheduledAnnotationBeanPostProcessor類等。
SchedulingConfigurer接口內容以下,只有一個抽象方法。
@EnableScheduling 註解的註釋裏也給出了例子,實現這個SchedulingConfigurer接口就能夠實現多線程定時任務。值得注意的是最後一段,將線程池交由spring容器管理,指定銷燬回調在Bean銷燬時調用線程池的shutdown方法,保證spring容器關閉前銷燬線程池中的線程,防止線程未終結而駐留。(若是不指定在linux tomcat中可能會使tomcat進程沒法經過shutdown命令關閉,致使內存泄露)
實現SchedulingConfigurer接口的configureTasks()。
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.SchedulingConfigurer; import org.springframework.scheduling.config.ScheduledTaskRegistrar; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; @Configuration public class ScheduleConfig implements SchedulingConfigurer { @Bean(destroyMethod="shutdownNow") public ScheduledExecutorService taskExecutors() { return Executors.newScheduledThreadPool(10); } @Override public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) { //參數傳入一個線程池 scheduledTaskRegistrar.setScheduler(taskExecutors()); } }
在項目部署到tomcat中,若是按照例子中建立線程池須要指定銷燬方法爲shutdownNow,shutdown會繼續執行而且完成全部未執行的任務,shutdownNow 會清除全部未執行的任務而且在運行線程上調用interrupt() 。
ps:博主因爲剛開始未設置銷燬方法爲shutdownNow,在centos上執行tomcat的shutdown命令沒法結束tomcat進程,tomcat結束線程在結束spring容器時,沒法結束線程池中線程,線程池中線程在spring容器銷燬後還未被殺死,從而致使tomcat進程一直駐留,錯誤信息以下:(web應用啓動了一個線程,但未能中止它,這極可能形成內存泄漏)
16-May-2019 23:19:15.018 INFO [main] org.apache.catalina.core.StandardServer.await A valid shutdown command was received via the shutdown port. Stopping the Server instance. 16-May-2019 23:19:15.019 INFO [main] org.apache.coyote.AbstractProtocol.pause Pausing ProtocolHandler ["http-nio-8080"] 16-May-2019 23:19:15.070 INFO [main] org.apache.coyote.AbstractProtocol.pause Pausing ProtocolHandler ["ajp-nio-8009"] 16-May-2019 23:19:15.121 INFO [main] org.apache.catalina.core.StandardService.stopInternal Stopping service [Catalina] 16-May-2019 23:19:15.240 WARNING [localhost-startStop-1] org.apache.catalina.loader.WebappClassLoaderBase.clearReferencesThreads The web application [kittyapi] appears to have started a thread named [pool-2-thread-1] but has failed to stop it. This is very likely to create a memory leak. Stack trace of thread: sun.misc.Unsafe.park(Native Method) java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:215) java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:2078) java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:1093) java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:809) java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1074) java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134) java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) java.lang.Thread.run(Thread.java:748)
以上是jdk的Executors建立的線程池,也可使用spring的線程池ThreadPoolTaskScheduler(ScheduledThreadPoolExecutor的包裝)來建立線程池,具體以下:
import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.SchedulingConfigurer; import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; import org.springframework.scheduling.config.ScheduledTaskRegistrar; import java.util.concurrent.Executor; @Slf4j @Configuration public class ScheduleConfig implements SchedulingConfigurer { /** * 定時任務線程池 * * @return */ @Bean("scheduledThreadPoolExecutor") public Executor scheduledThreadPoolExecutor() { log.info("start scheduledThreadPoolExecutor"); ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); // 配置核心線程數 scheduler.setPoolSize(10); return scheduler; } @Override public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) { // 參數傳入一個線程池 scheduledTaskRegistrar.setScheduler(scheduledThreadPoolExecutor()); } }
ThreadPoolTaskScheduler類的繼承和實現關係以下:
ThreadPoolTaskScheduler是TaskScheduler接口的實現類,它的父類ExecutorConfigurationSupport重寫了銷燬方法destroy( )和shutdown( ),經過查看源碼能夠看到,若是不設置等待任務在關閉容器時完成(waitForTasksToCompleteOnShutdown = true),那麼就默認調用了ScheduledThreadPoolExecutor(後續解釋爲何是這個類)類的shutdownNo方法
ThreadPoolTaskScheduler類是spring對jdk中ScheduledThreadPoolExecutor類的包裝,當建立一個ThreadPoolTaskScheduler對象的bean時,它的內部就已經自動建立了一個默認池大小爲1的ScheduledThreadPoolExecutor線程池對象,要注意是在spring容器註冊bean時纔會去初始化內部的線程池。
下面來看ScheduledTaskRegistrar類,能夠經過它裏面的方法設置TaskScheduler的子類也就是ThreadPoolTaskScheduler類,因此在實現SchedulingConfigurer接口時能夠經過調用ThreadPoolTaskScheduler中的set***方法注入外部線程池。
最後看看ScheduledAnnotationBeanPostProcessor這個類,它內部實例化了一個ScheduledTaskRegistrar對象。
接着往下看,在完成註冊的方法裏,若是有對象有TaskScheduler或者ScheduledExecutorService對象那麼直接使用該對象,往下走若是知足條件定時線程池爲null,那麼就調用BeanFactory先使用class類型去獲取容器中的TaskScheduler的子類,若是容器中存在多個TaskScheduler類型的bean,那麼使用默認bean名稱去獲取,默認名稱就是taskScheduler。
接着往下走,能夠看到,若是剛開始沒有設置TaskScheduler線程池,而且容器中也沒有註冊TaskScheduler線程池對象,那麼ScheduledTaskRegistrar會建立一個單線程線程池來執行定時任務。
總結以上,就會就會發現多線程實現只須要直接在spring容器中註冊一個TaskScheduler子類也就是ThreadPoolTaskScheduler就能夠了(若是存在多個,要指定bean名稱爲taskScheduler,spring默認使用此名稱的bean,這裏只是設置了線程數量,其餘屬性可根據具體需求設置),就不用去實現SchedulingConfigurer接口了。若是定時任務較多複雜,建議集成Quartz。
import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.TaskScheduler; import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; import java.util.concurrent.Executor; import java.util.concurrent.ThreadPoolExecutor; @Slf4j @Configuration public class ExecutorConfig { @Bean public TaskScheduler taskScheduler() { ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); scheduler.setPoolSize(10); return scheduler; } }