一、什麼是定時任務
開發中經常會使用到定時任務,顧名思義,定時任務就是定時執行的方法,即定時執行的程式碼。比如,為了減少伺服器或者資料庫的壓力,我們會將一些對伺服器或者資料庫等有壓力的高頻操作,改為定時去執行,例如每晚凌晨0點同步A系統的資料到B系統,每2小時統計使用者的積分情況,每週一給支付寶使用者推送上週收入支出資料包表等。一般情況下,很多業務處理會定時在凌晨處理,因為避開了使用者使用高峰期,伺服器資源充足,而且對使用者影響小。
作為優秀的框架,SpringBoot自然為我們提供了定時任務,有三種使用的方式,第一種是使用註解的方式(比較常用),這種不能動態更改定時任務的時間;第二種是可以動態更改定時任務的時間;第三種是可以動態手動啟動,停止以及更改定時任務時間的定時任務。
二、專案依賴
既然是SpringBoot提供的定時任務,那首先得引入Springboot相關的依賴,因為演示用到了介面呼叫,所以也引入web相關的依賴。然後演示專案採用Maven工程,最終依賴pom.xml如下:
<?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方法。
@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 "";
}
3.1 cron
String CRON_DISABLED = "-";
String cron() default "";
它的值是一個cron表示式字串,指明定時任務的執行時機。如果它的值是一個特殊的"-"字串,也就是CRON_DISABLED屬性定義的值,代表定時任務無效,不會執行。此特殊值主要用於外部指定值,即佔位符${...}時,可以通過配置檔案靈活控制定時任務的開啟停用。
此種方式最常用,而且cron的強大能讓我們涵蓋各種時間的配置。
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註解啟用,不然不生效。
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秒執行了定時任務。
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=-
3.2 fixedDelay
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...
3.3 fixedDelayString
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
3.4 fixedRate
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...
3.5 fixedRateString
String fixedRateString() default "";
此屬性表明,兩次定時任務呼叫之間間隔的毫秒數。即上一個呼叫開始後再次呼叫的延遲時間(不用等上一次呼叫完成)。與fixedRate相同,只不過值是字串的形式。但是它支援佔位符。
3.6 initialDelay 和 initialDelayString
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... ");
}
3.7 zone
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之言」技術文章持續更新,請持續關注......
- 第一時間學習最新技術文章
- 領取最新技術學習資料視訊
- 最新網際網路資訊和麵試經驗