你不知道的Scheduled定時任務騷操做

1、什麼是定時任務

開發中常常會使用到定時任務,顧名思義,定時任務就是定時執行的方法,即定時執行的代碼。好比,爲了減小服務器或者數據庫的壓力,咱們會將一些對服務器或者數據庫等有壓力的高頻操做,改成定時去執行,例如每晚凌晨0點同步A系統的數據到B系統,每2小時統計用戶的積分狀況,每週一給支付寶用戶推送上週收入支出數據報表等。通常狀況下,不少業務處理會定時在凌晨處理,由於避開了用戶使用高峯期,服務器資源充足,並且對用戶影響小。java

做爲優秀的框架,SpringBoot天然爲咱們提供了定時任務,有三種使用的方式,第一種是使用註解的方式(比較經常使用),這種不能動態更改定時任務的時間;第二種是能夠動態更改定時任務的時間;第三種是能夠動態手動啓動,中止以及更改定時任務時間的定時任務。git

2、項目依賴

既然是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>

3、註解式定時任務

這種方式很簡單,直接在須要定時執行的方法上加@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 "";
}

3.1 cron

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=-

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();
    }
}

在這裏插入圖片描述

4、可更改時間的定時任務

此種方式要實現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 ...

5、可啓動中止改變定時任務

此種方式能夠手動啓動,中止定時任務,以及能更改定時任務的執行時間。

其原理是利用線程池實現任務調度,能夠實現任務的調度和刪除。藉助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 ...

6、分佈式集羣注意事項

雖然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之言」技術文章持續更新,請持續關注......

  • 第一時間學習最新技術文章
  • 領取最新技術學習資料視頻
  • 最新互聯網資訊和麪試經驗
相關文章
相關標籤/搜索