一、什么是定时任务

开发中经常会使用到定时任务,顾名思义,定时任务就是定时执行的方法,即定时执行的代码。比如,为了减少服务器或者数据库的压力,我们会将一些对服务器或者数据库等有压力的高频操作,改为定时去执行,例如每晚凌晨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之言」技术文章持续更新,请持续关注......

  • 第一时间学习最新技术文章
  • 领取最新技术学习资料视频
  • 最新互联网资讯和面试经验

你不知道的Scheduled定时任务骚操作的更多相关文章

  1. 闪电侠 Netty 小册里的骚操作

    前言 即使这是一本小册,但基于"不提笔不读书"的理念,仍然有必要总结一下.此小册对于那些"硬杠 Netty 源码 却不曾在千万级生产环境上使用实操"的用户非常有 ...

  2. Spring Boot Scheduled定时任务特性

    SpringBoot中的Scheduled定时任务是Spring Boot中非常常用的特性,用来执行一些比如日切或者日终对账这种定时任务 下面说说使用时要注意的Scheduled的几个特性 Sched ...

  3. Typescript骚操作,在TS里面直接插入HTML

    Typescript骚操作,在TS里面直接插入HTML,还有语法提示 先给大家看一个图 因为我不喜欢用很重的框架,主要是并非专业UI,但是偶尔会用到,还是觉得直接element组装受不了,想想能在ts ...

  4. awk骚操作

    一.awk自加 [root@168web3 ~]# head /data/logs/cloud_monitor_rds_cpu.log |awk '{sum+=$NF}END{print sum}' ...

  5. 如何在命令长度受限的情况下成功get到webshell(函数参数受限突破、mysql的骚操作)

    0x01 问题提出 还记得上篇文章记一次拿webshell踩过的坑(如何用PHP编写一个不包含数字和字母的后门),我们讲到了一些PHP的一些如何巧妙地绕过数字和字母受限的技巧,今天我要给大家分享的是如 ...

  6. UOJ 117 欧拉回路(套圈法+欧拉回路路径输出+骚操作)

    题目链接:http://uoj.ac/problem/117 题目大意: 解题思路:先判断度数: 若G为有向图,欧拉回路的点的出度等于入度. 若G为无向图,欧拉回路的点的度数位偶数. 然后判断连通性, ...

  7. SpringBoot学习18:springboot使用Scheduled 定时任务器

    Scheduled 定时任务器:是 Spring3.0 以后自带的一个定时任务器. 1.在pom.xml文件中添加Scheduled依赖 <!-- 添加spring定时任务 Scheduled ...

  8. 关于map 及 map 骚操作

    关于map这个东西   很冷门..................   但是,这个博客带你稍微了解一下map:   map用法:一般当作一个下表无穷大的数组   关于它的骚操作:map的鬼畜用法,可以 ...

  9. 通过HTTP的HEADER完成各种骚操作

    作为一名专业的切图工程师,我从来不care网页的header,最多关心Status Code是不是200.但是HEADER真的很重要啊,客户端从服务器端获取内容,首先就是通过HEADER进行各种沟通! ...

随机推荐

  1. 2019牛客多校 Round1

    Solved:4 Rank:143 A Equivalent Prefixes 题意:求一个最大的r满足在A,B两个数组中1,r里所有的子区间RMQ相等 题解:单调队列秒 #include <b ...

  2. 【uva 11134】Fabled Rooks(算法效率--问题分解+贪心)

    题意:要求在一个N*N的棋盘上放N个车,使得它们所在的行和列均不同,而且分别处于第 i 个矩形中. 解法:问题分解+贪心. 由于行.列不相关,所以可以先把行和列均不同的问题分解为2个"在区间 ...

  3. 牛客编程巅峰赛S2第7场 - 钻石&王者 A.牛牛的独特子序列 (字符串,二分)

    题意:给你一个字符串,找出一个类似为\(aaabbbccc\)这样的由连续的\(abc\)构成的子序列,其中\(|a|=|b|=|c|\),问字符串中能构造出的子序列的最大长度. 题解:这题刚开始一直 ...

  4. Codeforces Round #672 (Div. 2) D. Rescue Nibel! (思维,组合数)

    题意:给你\(n\)个区间,从这\(n\)区间中选\(k\)个区间出来,要求这\(k\)个区间都要相交.问共有多少种情况. 题解:如果\(k\)个区间都要相交,最左边的区间和最右边的区间必须要相交,即 ...

  5. DNA Sequence POJ - 2778 AC自动机 && 矩阵快速幂

    It's well known that DNA Sequence is a sequence only contains A, C, T and G, and it's very useful to ...

  6. 6.PowerShell DSC核心概念之LCM

    什么是LCM? 本地配置管理器 (LCM) 是DSC的引擎. LCM 在每个目标节点上运行,负责分析和执行发送到节点的配置. 它还负责 DSC 的许多方面,包括以下各方面. 确定刷新模式(推送或请求) ...

  7. &#128218;C#/.NET/.NET Core推荐学习书籍(升职加薪,你值得拥有)

    前言: 作为一名程序员,我们无时无刻都要考虑着如何通过不断地学习来提升自己的核心竞争力.古人有云:"书中自有黄金屋,书中只有颜如玉",说明了书籍的重要性,没错工作多年来,发现身边那 ...

  8. iTerm2终端工具在Mac OS上使用详解

    一.概述 因个人工作需要,使用终端工具进行运维和开发工作,但是Mac OS 自带的终端工具使用堡垒机登录配置不了,而且使用CRT等终端工具每次登录堡垒机都需要配置密码,操作起来很麻烦.一直想找一款终端 ...

  9. Java中Class.forName()用法和newInstance()方法详解

    1.Class.forName()主要功能 Class.forName(xxx.xx.xx)返回的是一个类, Class.forName(xxx.xx.xx)的作用是要求JVM查找并加载指定的类,也就 ...

  10. 016.NET5_MVC_视图组件扩展定制

    视图组件 1. 呈现页面响应的某一部分而不是整个响应 2. 包括在控制器和视图之间发生的关注分类和可测试优势 3.可以具有参数和业务逻辑 4. 通常在页面局部调用 如何自定义视图组件? 1.Razor ...