一、背景介绍

在实际的业务开发过程中,我们经常会需要定时任务来帮助我们完成一些工作,例如每天早上 6 点生成销售报表、每晚 23 点清理脏数据等等。

如果你当前使用的是 SpringBoot 来开发项目,那么完成这些任务会非常容易!

SpringBoot 默认已经帮我们完成了相关定时任务组件的配置,我们只需要添加相应的注解@Scheduled就可以实现任务调度!

二、方案实践

2.1、pom 包配置

pom包里面只需要引入Spring Boot Starter包即可!

<dependencies>
<!--spring boot核心-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!--spring boot 测试-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

2.2、启动类启用定时调度

在启动类上面加上@EnableScheduling即可开启定时

@SpringBootApplication
@EnableScheduling
public class ScheduleApplication { public static void main(String[] args) {
SpringApplication.run(ScheduleApplication.class, args);
}
}

2.3、创建定时任务

Spring Scheduler支持四种形式的任务调度!

  • fixedRate:固定速率执行,例如每5秒执行一次
  • fixedDelay:固定延迟执行,例如距离上一次调用成功后2秒执行
  • initialDelay:初始延迟任务,例如任务开启过5秒后再执行,之后以固定频率或者间隔执行
  • cron:使用 Cron 表达式执行定时任务
2.3.1、固定速率执行

你可以通过使用fixedRate参数以固定时间间隔来执行任务,示例如下:

@Component
public class SchedulerTask { private static final Logger log = LoggerFactory.getLogger(SchedulerTask.class);
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); /**
* fixedRate:固定速率执行。每5秒执行一次。
*/
@Scheduled(fixedRate = 5000)
public void runWithFixedRate() {
log.info("Fixed Rate Task,Current Thread : {},The time is now : {}", Thread.currentThread().getName(), dateFormat.format(new Date()));
}
}

运行ScheduleApplication主程序,即可看到控制台输出效果:

Fixed Rate Task,Current Thread : scheduled-thread-1,The time is now : 2020-12-15 11:46:00
Fixed Rate Task,Current Thread : scheduled-thread-1,The time is now : 2020-12-15 11:46:10
...
2.3.2、固定延迟执行

你可以通过使用fixedDelay参数来设置上一次任务调用完成与下一次任务调用开始之间的延迟时间,示例如下:

@Component
public class SchedulerTask { private static final Logger log = LoggerFactory.getLogger(SchedulerTask.class);
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); /**
* fixedDelay:固定延迟执行。距离上一次调用成功后2秒后再执行。
*/
@Scheduled(fixedDelay = 2000)
public void runWithFixedDelay() {
log.info("Fixed Delay Task,Current Thread : {},The time is now : {}", Thread.currentThread().getName(), dateFormat.format(new Date()));
}
}

控制台输出效果:

Fixed Delay Task,Current Thread : scheduled-thread-1,The time is now : 2020-12-15 11:46:00
Fixed Delay Task,Current Thread : scheduled-thread-1,The time is now : 2020-12-15 11:46:02
...
2.3.3、初始延迟任务

你可以通过使用initialDelay参数与fixedRate或者fixedDelay搭配使用来实现初始延迟任务调度。

@Component
public class SchedulerTask { private static final Logger log = LoggerFactory.getLogger(SchedulerTask.class);
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); /**
* initialDelay:初始延迟。任务的第一次执行将延迟5秒,然后将以5秒的固定间隔执行。
*/
@Scheduled(initialDelay = 5000, fixedRate = 5000)
public void reportCurrentTimeWithInitialDelay() {
log.info("Fixed Rate Task with Initial Delay,Current Thread : {},The time is now : {}", Thread.currentThread().getName(), dateFormat.format(new Date()));
}
}

控制台输出效果:

Fixed Rate Task with Initial Delay,Current Thread : scheduled-thread-1,The time is now : 2020-12-15 11:46:05
Fixed Rate Task with Initial Delay,Current Thread : scheduled-thread-1,The time is now : 2020-12-15 11:46:10
...
2.3.4、使用 Cron 表达式

Spring Scheduler同样支持Cron表达式,如果以上简单参数都不能满足现有的需求,可以使用 cron 表达式来定时执行任务。

关于cron表达式的具体用法,可以点击参考这里: https://cron.qqe2.com/

@Component
public class SchedulerTask { private static final Logger log = LoggerFactory.getLogger(SchedulerTask.class);
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); /**
* cron:使用Cron表达式。每6秒中执行一次
*/
@Scheduled(cron = "*/6 * * * * ?")
public void reportCurrentTimeWithCronExpression() {
log.info("Cron Expression,Current Thread : {},The time is now : {}", Thread.currentThread().getName(), dateFormat.format(new Date()));
}
}

控制台输出效果:

Cron Expression,Current Thread : scheduled-thread-1,The time is now : 2020-12-15 11:46:06
Cron Expression,Current Thread : scheduled-thread-1,The time is now : 2020-12-15 11:46:12
...

2.4、异步执行定时任务

在介绍异步执行定时任务之前,我们先看一个例子!

在下面的示例中,我们创建了一个每隔2秒执行一次的定时任务,在任务里面大概需要花费 3 秒钟,猜猜执行结果如何?

@Component
public class AsyncScheduledTask { private static final Logger log = LoggerFactory.getLogger(AsyncScheduledTask.class);
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); @Scheduled(fixedRate = 2000)
public void runWithFixedDelay() {
try {
TimeUnit.SECONDS.sleep(3);
log.info("Fixed Delay Task, Current Thread : {} : The time is now {}", Thread.currentThread().getName(), dateFormat.format(new Date()));
} catch (InterruptedException e) {
log.error("错误信息",e);
}
}
}

控制台输入结果:

Fixed Delay Task, Current Thread : scheduling-1 : The time is now 2020-12-15 17:55:26
Fixed Delay Task, Current Thread : scheduling-1 : The time is now 2020-12-15 17:55:31
Fixed Delay Task, Current Thread : scheduling-1 : The time is now 2020-12-15 17:55:36
Fixed Delay Task, Current Thread : scheduling-1 : The time is now 2020-12-15 17:55:41
...

很清晰的看到,任务调度频率变成了每隔5秒调度一次!

这是为啥呢?

Current Thread : scheduling-1输出结果可以很看到,任务执行都是同一个线程!默认的情况下,@Scheduled任务都在 Spring 创建的大小为 1 的默认线程池中执行!

更直观的结果是,任务都是串行执行!

下面,我们将其改成异步线程来执行,看看效果如何?

@Component
@EnableAsync
public class AsyncScheduledTask { private static final Logger log = LoggerFactory.getLogger(AsyncScheduledTask.class);
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); @Async
@Scheduled(fixedDelay = 2000)
public void runWithFixedDelay() {
try {
TimeUnit.SECONDS.sleep(3);
log.info("Fixed Delay Task, Current Thread : {} : The time is now {}", Thread.currentThread().getName(), dateFormat.format(new Date()));
} catch (InterruptedException e) {
log.error("错误信息",e);
}
}
}

控制台输出结果:

Fixed Delay Task, Current Thread : SimpleAsyncTaskExecutor-1 : The time is now 2020-12-15 18:55:26
Fixed Delay Task, Current Thread : SimpleAsyncTaskExecutor-2 : The time is now 2020-12-15 18:55:28
Fixed Delay Task, Current Thread : SimpleAsyncTaskExecutor-3 : The time is now 2020-12-15 18:55:30
...

任务的执行频率不受方法内的时间影响,以并行方式执行!

2.5、自定义任务线程池

虽然默认的情况下,@Scheduled任务都在 Spring 创建的大小为 1 的默认线程池中执行,但是我们也可以自定义线程池,只需要实现SchedulingConfigurer类即可!

自定义线程池示例如下:

@Configuration
public class SchedulerConfig implements SchedulingConfigurer { @Override
public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
//线程池大小为10
threadPoolTaskScheduler.setPoolSize(10);
//设置线程名称前缀
threadPoolTaskScheduler.setThreadNamePrefix("scheduled-thread-");
//设置线程池关闭的时候等待所有任务都完成再继续销毁其他的Bean
threadPoolTaskScheduler.setWaitForTasksToCompleteOnShutdown(true);
//设置线程池中任务的等待时间,如果超过这个时候还没有销毁就强制销毁,以确保应用最后能够被关闭,而不是阻塞住
threadPoolTaskScheduler.setAwaitTerminationSeconds(60);
//这里采用了CallerRunsPolicy策略,当线程池没有处理能力的时候,该策略会直接在 execute 方法的调用线程中运行被拒绝的任务;如果执行程序已关闭,则会丢弃该任务
threadPoolTaskScheduler.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
threadPoolTaskScheduler.initialize(); scheduledTaskRegistrar.setTaskScheduler(threadPoolTaskScheduler);
}
}

我们启动服务,看看cron任务示例调度效果:

Cron Expression,Current Thread : scheduled-thread-1,The time is now : 2020-12-15 20:46:00
Cron Expression,Current Thread : scheduled-thread-2,The time is now : 2020-12-15 20:46:06
Cron Expression,Current Thread : scheduled-thread-3,The time is now : 2020-12-15 20:46:12
Cron Expression,Current Thread : scheduled-thread-4,The time is now : 2020-12-15 20:46:18
....

当前线程名称已经被改成自定义scheduled-thread的前缀!

三、小结

本文主要围绕Spring scheduled应用实践进行分享,如果是单体应用,使用SpringBoot内置的@scheduled注解可以解决大部分业务需求,上手非常容易!

项目源代码地址:spring-boot-example-scheduled

四、参考

1、https://springboot.io/t/topic/2758

3分钟带你搞定Spring Boot中Schedule的更多相关文章

  1. 3步轻松搞定Spring Boot缓存

    作者:谭朝红 前言 本次内容主要介绍基于Ehcache 3.0来快速实现Spring Boot应用程序的数据缓存功能.在Spring Boot应用程序中,我们可以通过Spring Caching来快速 ...

  2. 【项目实践】一文带你搞定Spring Security + JWT

    以项目驱动学习,以实践检验真知 前言 关于认证和授权,R之前已经写了两篇文章: [项目实践]在用安全框架前,我想先让你手撸一个登陆认证 [项目实践]一文带你搞定页面权限.按钮权限以及数据权限 在这两篇 ...

  3. 一文搞定Spring Boot + Vue 项目在Linux Mysql环境的部署(强烈建议收藏)

    本文介绍Spring Boot.Vue .Vue Element编写的项目,在Linux下的部署,系统采用Mysql数据库.按照本文进行项目部署,不迷路. 1. 前言 典型的软件开发,经过" ...

  4. 不用找了,300 分钟帮你搞定 Spring Cloud!

    最近几年,微服务架构一跃成为 IT 领域炙手可热的话题,大量一线互联网公司因为庞大的业务体量和业务需求,纷纷投入了微服务架构的建设中,像阿里巴巴.百度.美团等大厂,很早就已经开始了微服务的实践和应用. ...

  5. 一行配置搞定 Spring Boot项目的 log4j2 核弹漏洞!

    相信昨天,很多小伙伴都因为Log4j2的史诗级漏洞忙翻了吧? 看到群里还有小伙伴说公司里还特别建了800+人的群在处理... 好在很快就有了缓解措施和解决方案.同时,log4j2官方也是速度影响发布了 ...

  6. 一道题带你搞定Python函数中形参和实参问题

    昨天在Python学习群里有位路人甲问了个Python函数中关于形参和实参一个很基础的问题,虽然很基础,但是对于很多小白来说不一定简单,反而会被搞得稀里糊涂.人生苦短,我用Python. 为了解答大家 ...

  7. 是时候搞清楚 Spring Boot 的配置文件 application.properties 了!

    在 Spring Boot 中,配置文件有两种不同的格式,一个是 properties ,另一个是 yaml . 虽然 properties 文件比较常见,但是相对于 properties 而言,ya ...

  8. 第二篇:彻底搞清楚 Spring Boot 的配置文件 application.properties

    前言 在Spring Boot中,配置文件有两种不同的格式,一个是properties,另一个是yaml. 虽然properties文件比较常见,但是相对于properties而言,yaml更加简洁明 ...

  9. Spring Boot 中的同一个 Bug,竟然把我坑了两次!

    真是郁闷,不过这事又一次提醒我解决问题还是要根治,不能囫囵吞枣,否则相同的问题可能会以不同的形式出现,每次都得花时间去搞.刨根问底,一步到位,再遇到类似问题就可以分分钟解决了. 如果大家没看过松哥之前 ...

  10. Spring Boot2 系列教程(五)Spring Boot中的 yaml 配置

    搞 Spring Boot 的小伙伴都知道,Spring Boot 中的配置文件有两种格式,properties 或者 yaml,一般情况下,两者可以随意使用,选择自己顺手的就行了,那么这两者完全一样 ...

随机推荐

  1. golang sync.once done 热路径

    sync.once 为什么会将done放在结构体第一个字段,就能够提升性能了? 我们先来看看sync.once的结构体: // Once is an object that will perform ...

  2. 使用rem、动态vh自适应移动端

    前言 这是我的 模仿抖音 系列文章的第六篇 第一篇:200行代码实现类似Swiper.js的轮播组件 第二篇:实现抖音 "视频无限滑动"效果 第三篇:Vue 路由使用介绍以及添加转 ...

  3. NOIP模拟68

    T1 玩水 解题思路 我们称一个点合法当且仅当 \(s_{i-1,j}=s_{i,j-1}\) . 于是如果一个合法点在它的左上或者右下也可以相邻有一个合法点就是一个合法的图. 树状数组维护即可 co ...

  4. ajax 请求接口实例

    通过 HTTP 请求加载远程数据. jQuery 底层 AJAX 实现.简单易用的高层实现见 $.get, $.post 等.$.ajax() 返回其创建的 XMLHttpRequest 对象.大多数 ...

  5. Java常用的三个方法 `wait ` `notify` `notifyAll`

    常用的三个方法 wait notify notifyAll wait();方法使当前线程进入等待状态,直到另一个线程调用该对象的notify()或notifyAll()方法来唤醒它 notify(); ...

  6. Android Media Framework(三)OpenMAX API阅读与分析

    这篇文章我们将聚焦Control API的功能与用法,为实现OMX Core.Component打下坚实的基础. 1.OMX_Core.h OMX Core在OpenMAX IL架构中的位置位于IL ...

  7. 比较 SpringSecurity 和 Shiro

    相比 Spring Security, Shiro 在保持强大功能的同时,使用简单性和灵活性. SpringSecurity: 即使是一个一个简单的请求, 最少得经过它的 8 个Filter.Spri ...

  8. 【大道至简】NetCore3.1快速开发框架一:介绍

    新的一年开始,祝大家2020新年快乐! 去年开源了一个基于NetCore2.2的框架,好多好基友还是比较喜欢的 github地址:https://github.com/feiyit/FytSoaCms ...

  9. http请求方式-OkHttpClient

    http请求方式-OkHttpClient import com.example.core.mydemo.http.OrderReqVO; import okhttp3.*; import org.s ...

  10. C# .NET 常见DeepCopy 深度拷贝的性能对比

    先上结论 Method Mean Error StdDev Gen0 Gen1 Allocated JSONConvert 2,273.02 ns 43.758 ns 52.091 ns 0.6599 ...