開發中常常會使用到定時任務,顧名思義,定時任務就是定時執行的方法,即定時執行的代碼。好比,爲了減小服務器或者數據庫的壓力,咱們會將一些對服務器或者數據庫等有壓力的高頻操做,改成定時去執行,例如每晚凌晨0點同步A系統的數據到B系統,每2小時統計用戶的積分狀況,每週一給支付寶用戶推送上週收入支出數據報表等。通常狀況下,不少業務處理會定時在凌晨處理,由於避開了用戶使用高峯期,服務器資源充足,並且對用戶影響小。java
做爲優秀的框架,SpringBoot天然爲咱們提供了定時任務,有三種使用的方式,第一種是使用註解的方式(比較經常使用),這種不能動態更改定時任務的時間;第二種是能夠動態更改定時任務的時間;第三種是能夠動態手動啓動,中止以及更改定時任務時間的定時任務。git
既然是SpringBoot提供的定時任務,那首先得引入Springboot相關的依賴,由於演示用到了接口調用,因此也引入web相關的依賴。而後演示項目採用Maven工程,最終依賴pom.xml以下:github
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.10.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.nobody</groupId> <artifactId>scheduled-task</artifactId> <version>0.0.1-SNAPSHOT</version> <name>scheduled-task</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
這種方式很簡單,直接在須要定時執行的方法上加@Scheduled註解便可。以下表示天天凌晨0點執行test方法。web
@Scheduled(cron = "0 0 0 * * ? ") public void test() { // doSomething }
@Scheduled註解有幾個屬性,咱們一一講解它的做用。面試
package org.springframework.scheduling.annotation; @Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Repeatable(Schedules.class) public @interface Scheduled { String CRON_DISABLED = "-"; String cron() default ""; String zone() default ""; long fixedDelay() default -1; String fixedDelayString() default ""; long fixedRate() default -1; String fixedRateString() default ""; long initialDelay() default -1; String initialDelayString() default ""; }
String CRON_DISABLED = "-"; String cron() default "";
它的值是一個cron表達式字符串,指明定時任務的執行時機。若是它的值是一個特殊的"-"字符串,也就是CRON_DISABLED屬性定義的值,表明定時任務無效,不會執行。此特殊值主要用於外部指定值,即佔位符${...}時,能夠經過配置文件靈活控制定時任務的開啓停用。redis
此種方式最經常使用,並且cron的強大能讓咱們涵蓋各類時間的配置。spring
cron表達式我就不細講了,下面推薦一個方便生成cron的網站:https://cron.qqe2.com/
注意,定時任務所在的類,要將其交予Spring容器管理,最簡單的是在類上添加@Component註解,以下所示:數據庫
package com.nobody.task; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; /** * @Description 定時任務類 * @Author Mr.nobody * @Date 2021/3/2 * @Version 1.0.0 */ @Component public class ScheduledTask { private static final Logger LOGGER = LoggerFactory.getLogger(ScheduledTask.class); // 每5秒執行一次 @Scheduled(cron = "0/5 * * * * ? ") public void test() { LOGGER.info(">>> ScheduledTask test... "); } }
並且要經過@EnableScheduling註解激活,否則不生效。express
package com.nobody; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.scheduling.annotation.EnableScheduling; @SpringBootApplication @EnableScheduling public class ScheduledTaskApplication { public static void main(String[] args) { SpringApplication.run(ScheduledTaskApplication.class, args); } }
咱們啓動服務,能夠在控制看到每隔5秒執行了定時任務。apache
2021-03-02 23:44:00.005 INFO 10852 --- [TaskScheduler-1] com.nobody.task.ScheduledTask : >>> ScheduledTask test... 2021-03-02 23:44:05.001 INFO 10852 --- [TaskScheduler-1] com.nobody.task.ScheduledTask : >>> ScheduledTask test... 2021-03-02 23:44:10.000 INFO 10852 --- [TaskScheduler-1] com.nobody.task.ScheduledTask : >>> ScheduledTask test... 2021-03-02 23:44:15.002 INFO 10852 --- [TaskScheduler-1] com.nobody.task.ScheduledTask : >>> ScheduledTask test... 2021-03-02 23:44:20.001 INFO 10852 --- [TaskScheduler-1] com.nobody.task.ScheduledTask : >>> ScheduledTask test...
前面說了cron的值能夠經過外部配置文件的形式指定,以下:
package com.nobody.task; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; /** * @Description 定時任務類 * @Author Mr.nobody * @Date 2021/3/2 * @Version 1.0.0 */ @Component public class ScheduledTask { private static final Logger LOGGER = LoggerFactory.getLogger(ScheduledTask.class); // 每5秒執行一次 @Scheduled(cron = "${cron.exp}") public void test() { LOGGER.info(">>> ScheduledTask test... "); } }
而後在配置文件application.properties中填寫配置變量的值,此種方式比較靈活,不用修改代碼便可更改時間。並且若是將值改成"-",表明定時任務無效。
cron.exp=0/5 * * * * ? #cron.exp=-
long fixedDelay() default -1;
此屬性代表,從上次定時任務執行完後,延遲多久再次執行定時任務。以毫秒爲單位。
package com.nobody.task; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; /** * @Description 定時任務類 * @Author Mr.nobody * @Date 2021/3/2 * @Version 1.0.0 */ @Component public class ScheduledTask { private static final Logger LOGGER = LoggerFactory.getLogger(ScheduledTask.class); // 延遲1秒 @Scheduled(fixedDelay = 1000) public void test() { try { // 休眠2秒 Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } LOGGER.info(">>> ScheduledTask test... "); } }
輸出結果以下,恰好兩次執行時間間隔3秒(2秒休眠+1秒延遲)。
2021-03-03 00:03:44.025 INFO 15612 --- [TaskScheduler-1] com.nobody.task.ScheduledTask : >>> ScheduledTask test... 2021-03-03 00:03:47.027 INFO 15612 --- [TaskScheduler-1] com.nobody.task.ScheduledTask : >>> ScheduledTask test... 2021-03-03 00:03:50.029 INFO 15612 --- [TaskScheduler-1] com.nobody.task.ScheduledTask : >>> ScheduledTask test... 2021-03-03 00:03:53.031 INFO 15612 --- [TaskScheduler-1] com.nobody.task.ScheduledTask : >>> ScheduledTask test...
String fixedDelayString() default ""
此屬性代表,從上次定時任務執行完後,延遲多久再次執行定時任務。以毫秒爲單位。與fixedDelay做用相同,只不過值是字符串的形式。可是它支持佔位符。
package com.nobody.task; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; /** * @Description 定時任務類 * @Author Mr.nobody * @Date 2021/3/2 * @Version 1.0.0 */ @Component public class ScheduledTask { private static final Logger LOGGER = LoggerFactory.getLogger(ScheduledTask.class); // 延遲1秒 @Scheduled(fixedDelayString = "1000") public void test() { try { // 休眠2秒 Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } LOGGER.info(">>> ScheduledTask test... "); } }
輸出結果以下,恰好兩次執行時間間隔3秒(2秒休眠+1秒延遲)。
2021-03-03 00:09:58.234 INFO 9644 --- [TaskScheduler-1] com.nobody.task.ScheduledTask : >>> ScheduledTask test... 2021-03-03 00:10:01.238 INFO 9644 --- [TaskScheduler-1] com.nobody.task.ScheduledTask : >>> ScheduledTask test... 2021-03-03 00:10:04.262 INFO 9644 --- [TaskScheduler-1] com.nobody.task.ScheduledTask : >>> ScheduledTask test... 2021-03-03 00:10:07.340 INFO 9644 --- [TaskScheduler-1] com.nobody.task.ScheduledTask : >>> ScheduledTask test...
使用佔位符以下所示,而且在配置文件application.properties中指定配置變量的值。
@Scheduled(fixedDelayString = "${fixed.delay}")
fixed.delay=1000
long fixedRate() default -1;
此屬性代表,兩次定時任務調用之間間隔的毫秒數。即上一個調用開始後再次調用的延遲時間(不用等上一次調用完成)。
可是默認狀況下是使用單線程是來執行全部定時任務的,因此即便前一個調用還未執行完,下一個調用已經開始了,那它也得等上一個調用執行完了,才能執行下一個。
@Scheduled(fixedRate = 1000) public void test() { try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } LOGGER.info(">>> ScheduledTask test... "); }
上述兩次定時任務調用之間間隔爲1秒,可是執行時間爲5秒,可是發現它們間隔執行時間仍是5秒,並且打印出的都是同一個線程名TaskScheduler-1,證實了默認狀況下確實如此。
2021-03-03 00:20:35.307 INFO 16152 --- [TaskScheduler-1] com.nobody.task.ScheduledTask : >>> ScheduledTask test... 2021-03-03 00:20:40.309 INFO 16152 --- [TaskScheduler-1] com.nobody.task.ScheduledTask : >>> ScheduledTask test... 2021-03-03 00:20:45.309 INFO 16152 --- [TaskScheduler-1] com.nobody.task.ScheduledTask : >>> ScheduledTask test... 2021-03-03 00:20:50.310 INFO 16152 --- [TaskScheduler-1] com.nobody.task.ScheduledTask : >>> ScheduledTask test...
可是咱們能夠自定義線程池,而後經過@Async註解使用自定義的線程池異步執行,這樣就能達到多線程執行。可是若是是定時任務是執行相同業務操做,例如計算用戶的積分數,可能會出現併發操做的問題,因此不建議使用。但若是執行時間小於兩次調度的時間間隔仍是能夠考慮使用的。
@Scheduled(fixedRate = 1000) @Async("myExecutor") public void test() { try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } LOGGER.info(">>> ScheduledTask test... "); }
線程池配置類代碼以下:
package com.nobody.config; import java.util.concurrent.Executor; import java.util.concurrent.ThreadPoolExecutor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; @Configuration public class ExecutorConfig { public static final int CORE_POOL_SIZE = 5; public static final int MAX_POOL_SIZE = 15; public static final int QUEUE_CAPACITY = 100; @Bean("myExecutor") public Executor asyncServiceExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); // 核心線程數大小 executor.setCorePoolSize(CORE_POOL_SIZE); // 最大線程數大小 executor.setMaxPoolSize(MAX_POOL_SIZE); // 阻塞隊列容量 executor.setQueueCapacity(QUEUE_CAPACITY); // 線程名前綴 executor.setThreadNamePrefix("myTask-"); // rejectionPolicy:當queue達到maxSize而且此時maxPoolSize也達到最大值的時候,對於新任務的處理策略 // CallerRunsPolicy:不在新線程中執行任務,而是交由調用者所在的線程來執行 executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); executor.initialize(); return executor; } }
須要添加@EnableAsync註解激活。
package com.nobody; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.annotation.EnableScheduling; @SpringBootApplication @EnableScheduling @EnableAsync public class ScheduledTaskApplication { public static void main(String[] args) { SpringApplication.run(ScheduledTaskApplication.class, args); } }
最終輸出結果以下,發現間隔不是5秒,而是1秒了,並且不是單線程執行定時任務,是經過配置的線程池來執行的。
2021-03-03 00:36:41.010 INFO 5752 --- [ myTask-1] com.nobody.task.ScheduledTask : >>> ScheduledTask test... 2021-03-03 00:36:41.993 INFO 5752 --- [ myTask-2] com.nobody.task.ScheduledTask : >>> ScheduledTask test... 2021-03-03 00:36:42.998 INFO 5752 --- [ myTask-3] com.nobody.task.ScheduledTask : >>> ScheduledTask test... 2021-03-03 00:36:43.991 INFO 5752 --- [ myTask-4] com.nobody.task.ScheduledTask : >>> ScheduledTask test... 2021-03-03 00:36:44.993 INFO 5752 --- [ myTask-5] com.nobody.task.ScheduledTask : >>> ScheduledTask test... 2021-03-03 00:36:46.013 INFO 5752 --- [ myTask-1] com.nobody.task.ScheduledTask : >>> ScheduledTask test... 2021-03-03 00:36:47.023 INFO 5752 --- [ myTask-2] com.nobody.task.ScheduledTask : >>> ScheduledTask test... 2021-03-03 00:36:47.999 INFO 5752 --- [ myTask-3] com.nobody.task.ScheduledTask : >>> ScheduledTask test... 2021-03-03 00:36:48.992 INFO 5752 --- [ myTask-4] com.nobody.task.ScheduledTask : >>> ScheduledTask test... 2021-03-03 00:36:50.020 INFO 5752 --- [ myTask-5] com.nobody.task.ScheduledTask : >>> ScheduledTask test... 2021-03-03 00:36:51.013 INFO 5752 --- [ myTask-1] com.nobody.task.ScheduledTask : >>> ScheduledTask test... 2021-03-03 00:36:52.025 INFO 5752 --- [ myTask-2] com.nobody.task.ScheduledTask : >>> ScheduledTask test...
String fixedRateString() default "";
此屬性代表,兩次定時任務調用之間間隔的毫秒數。即上一個調用開始後再次調用的延遲時間(不用等上一次調用完成)。與fixedRate相同,只不過值是字符串的形式。可是它支持佔位符。
long initialDelay() default -1;
initialDelay此屬性代表,第一次執行fixedRate或fixedDelay任務以前要延遲的毫秒數。需配合fixedDelay或者fixedRate一塊兒使用。而initialDelayString是字符串的形式,而且支持佔位符。
// 延遲3秒纔開始執行第一次任務 @Scheduled(fixedDelayString = "1000", initialDelay = 3000) public void test() { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } LOGGER.info(">>> ScheduledTask test... "); }
String zone() default "";
時區,cron表達式會基於該時區解析。默認是一個空字符串,即取服務器所在地的時區。它的值是一個時區ID,咱們通常使用的時區是Asia/Shanghai。此屬性通常默認便可。
@Scheduled(cron = "0/5 * * * * ?", zone = "Asia/Shanghai") public void test() { TimeZone defaultTimeZone = TimeZone.getDefault(); LOGGER.info(">>> ScheduledTask test... " + defaultTimeZone.getID()); // 打印出可取得的全部時區ID String[] availableIDs = TimeZone.getAvailableIDs(); System.out.println(Arrays.toString(availableIDs)); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } }
此種方式要實現SchedulingConfigurer接口,而且重寫configureTasks方法。
package com.nobody.task; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.scheduling.Trigger; import org.springframework.scheduling.annotation.SchedulingConfigurer; import org.springframework.scheduling.config.ScheduledTaskRegistrar; import org.springframework.scheduling.support.CronTrigger; import org.springframework.stereotype.Component; /** * @Description 可動態更改時間的定時任務 * @Author Mr.nobody * @Date 2021/3/2 * @Version 1.0.0 */ @Component public class ChangeTimeScheduledTask implements SchedulingConfigurer { private static final Logger LOGGER = LoggerFactory.getLogger(ChangeTimeScheduledTask.class); // cron表達式,咱們動態更改此屬性的值便可更改定時任務的執行時間 private String expression = "0/5 * * * * *"; @Override public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { // 定時任務要執行的方法 Runnable task = () -> LOGGER.info(">>> configureTasks ..."); // 調度實現的時間控制 Trigger trigger = triggerContext -> { CronTrigger cronTrigger = new CronTrigger(expression); return cronTrigger.nextExecutionTime(triggerContext); }; taskRegistrar.addTriggerTask(task, trigger); } public String getExpression() { return expression; } public void setExpression(String expression) { this.expression = expression; } }
而後咱們編寫一個接口進行調用,動態改變定時任務的時間。
package com.nobody.controller; import com.nobody.task.ChangeTimeScheduledTask; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * @Description * @Author Mr.nobody * @Date 2021/3/2 * @Version 1.0.0 */ @RestController @RequestMapping("demo") public class DemoController { private ChangeTimeScheduledTask changeTimeScheduledTask; public DemoController(final ChangeTimeScheduledTask changeTimeScheduledTask) { this.changeTimeScheduledTask = changeTimeScheduledTask; } @GetMapping public String testChangeTimeScheduledTask() { changeTimeScheduledTask.setExpression("0/10 * * * * *"); return "ok"; } }
啓動服務,沒調用接口以前,定時任務是每5秒執行一次。
2021-03-03 13:56:20.001 INFO 6836 --- [TaskScheduler-1] com.nobody.task.ChangeTimeScheduledTask : >>> configureTasks ... 2021-03-03 13:56:25.001 INFO 6836 --- [TaskScheduler-1] com.nobody.task.ChangeTimeScheduledTask : >>> configureTasks ... 2021-03-03 13:56:30.002 INFO 6836 --- [TaskScheduler-1] com.nobody.task.ChangeTimeScheduledTask : >>> configureTasks ... 2021-03-03 13:56:35.001 INFO 6836 --- [TaskScheduler-1] com.nobody.task.ChangeTimeScheduledTask : >>> configureTasks ...
而後咱們調用接口,改變定時任務的時間,結果變爲每10秒執行一次。
2021-03-03 13:56:40.005 INFO 6836 --- [TaskScheduler-1] com.nobody.task.ChangeTimeScheduledTask : >>> configureTasks ... 2021-03-03 13:56:50.002 INFO 6836 --- [TaskScheduler-1] com.nobody.task.ChangeTimeScheduledTask : >>> configureTasks ... 2021-03-03 13:57:00.001 INFO 6836 --- [TaskScheduler-1] com.nobody.task.ChangeTimeScheduledTask : >>> configureTasks ...
此種方式能夠手動啓動,中止定時任務,以及能更改定時任務的執行時間。
其原理是利用線程池實現任務調度,能夠實現任務的調度和刪除。藉助ThreadPoolTaskScheduler線程池任務調度器,可以開啓線程池進行任務調度。經過ThreadPoolTaskScheduler的schedule方法建立一個定時計劃ScheduleFuture,ScheduleFuture中有一個cancel方法能夠中止定時任務。schedule方法中有2個參數,一個是Runnable task,線程接口類,即咱們要定時執行的方法,另外一個參數是Trigger trigger,定時任務觸發器,帶有cron值。
package com.nobody.task; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; import org.springframework.scheduling.support.CronTrigger; import org.springframework.stereotype.Component; import java.util.concurrent.ScheduledFuture; /** * @Description * @Author Mr.nobody * @Date 2021/3/2 * @Version 1.0.0 */ @Component public class DynamicScheduledTask { private static final Logger LOGGER = LoggerFactory.getLogger(DynamicScheduledTask.class); private ThreadPoolTaskScheduler threadPoolTaskScheduler; public DynamicScheduledTask(final ThreadPoolTaskScheduler threadPoolTaskScheduler) { this.threadPoolTaskScheduler = threadPoolTaskScheduler; } private ScheduledFuture future; /** * 啓動定時器 */ public void startTask() { // 第一個參數爲定時任務要執行的方法,第二個參數爲定時任務執行的時間 future = threadPoolTaskScheduler.schedule(this::test, new CronTrigger("0/5 * * * * *")); } /** * 中止定時器 */ public void endTask() { if (future != null) { future.cancel(true); } } /** * 改變調度的時間,先中止定時器再啓動新的定時器 */ public void changeTask() { // 中止定時器 endTask(); // 定義新的執行時間,並啓動 future = threadPoolTaskScheduler.schedule(this::test, new CronTrigger("0/10 * * * * *")); } /** * 定時任務執行的方法 */ public void test() { LOGGER.info(">>> DynamicScheduledTask ..."); } }
咱們須要建立ThreadPoolTaskScheduler實例,並交給Spring容器管理。
package com.nobody.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; /** * @Description * @Author Mr.nobody * @Date 2021/3/2 * @Version 1.0.0 */ @Configuration public class ThreadPoolTaskSchedulerConfig { @Bean public ThreadPoolTaskScheduler threadPoolTaskScheduler() { return new ThreadPoolTaskScheduler(); } }
最後編寫接口,對啓動,中止,更改時間進行調用便可。
package com.nobody.controller; import com.nobody.task.DynamicScheduledTask; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * @Description * @Author Mr.nobody * @Date 2021/3/2 * @Version 1.0.0 */ @RestController @RequestMapping("demo") public class DemoController { private DynamicScheduledTask dynamicScheduledTask; public DemoController(final DynamicScheduledTask dynamicScheduledTask) { this.dynamicScheduledTask = dynamicScheduledTask; } @GetMapping("startDynamicScheduledTask") public String startDynamicScheduledTask() { dynamicScheduledTask.startTask(); return "ok"; } @GetMapping("endDynamicScheduledTask") public String endDynamicScheduledTask() { dynamicScheduledTask.endTask(); return "ok"; } @GetMapping("changeDynamicScheduledTask") public String changeDynamicScheduledTask() { dynamicScheduledTask.changeTask(); return "ok"; } }
啓動服務,由於沒有調用啓動定時器接口,因此定時任務不會執行。只有調用了啓動的接口,定時任務纔開始執行。在服務運行期間,可任意進行定時任務的開啓,中止和更改時間操做。
2021-03-03 14:11:35.000 INFO 8548 --- [TaskScheduler-1] com.nobody.task.DynamicScheduledTask : >>> DynamicScheduledTask ... 2021-03-03 14:11:40.002 INFO 8548 --- [TaskScheduler-1] com.nobody.task.DynamicScheduledTask : >>> DynamicScheduledTask ... 2021-03-03 14:11:45.001 INFO 8548 --- [TaskScheduler-1] com.nobody.task.DynamicScheduledTask : >>> DynamicScheduledTask ... 2021-03-03 14:11:50.001 INFO 8548 --- [TaskScheduler-1] com.nobody.task.DynamicScheduledTask : >>> DynamicScheduledTask ... 2021-03-03 14:11:55.002 INFO 8548 --- [TaskScheduler-1] com.nobody.task.DynamicScheduledTask : >>> DynamicScheduledTask ... // 如下是更改了執行時間爲10秒 2021-03-03 14:12:00.001 INFO 8548 --- [TaskScheduler-1] com.nobody.task.DynamicScheduledTask : >>> DynamicScheduledTask ... 2021-03-03 14:12:10.001 INFO 8548 --- [TaskScheduler-1] com.nobody.task.DynamicScheduledTask : >>> DynamicScheduledTask ... 2021-03-03 14:12:20.001 INFO 8548 --- [TaskScheduler-1] com.nobody.task.DynamicScheduledTask : >>> DynamicScheduledTask ...
雖然Scheduled Task是一種輕量級的任務定時調度器,相比於Quartz減小了不少的配置信息。可是Scheduled Task 的有個缺點是不適用於分佈式集羣的操做,由於集羣的節點之間是不會共享任務信息的,會致使在多個服務器上執行相同重複的定時任務。
若是在多個服務器上執行相同的定時任務,對你的業務不影響那還好。但有些業務不容許重複執行,那咱們其實能夠經過分佈式鎖,只讓其中一個拿到鎖的節點來執行定時任務。
@Scheduled(cron = "${cron.exp}") public void test() { String lockKey = RedisKeyUtil.genKey(RedisKeyUtil.SCHEDULED_TASK_LOCK); boolean lockSuccess = redisUtils.getLock(lockKey, "1", 30000); if (!lockSuccess) { LOGGER.warn(">>> Scheduled is running on another server..."); } try { // doSomething(); } finally { redisUtils.releaseLock(lockKey, "1"); } }
此演示項目已上傳到Github,若有須要可自行下載,歡迎 Star 。
https://github.com/LucioChn/spring
歡迎關注微信公衆號:「Java之言」技術文章持續更新,請持續關注......
- 第一時間學習最新技術文章
- 領取最新技術學習資料視頻
- 最新互聯網資訊和麪試經驗