背景


在项目开发过程中,我们经常需要执行具有周期性的任务。通过定时任务可以很好的帮助我们实现。

我们拿常用的几种定时任务框架做一个比较:

从以上表格可以看出,Spring Schedule框架功能完善,简单易用。对于中小型项目需求,Spring Schedule是完全可以胜任的。

1、springboot集成schedule


1.1 添加maven依赖包

由于Spring Schedule包含在spring-boot-starter基础模块中了,所有不需要增加额外的依赖。

  1. <dependencies>
  2. <dependency>
  3. <groupId>org.springframework.boot</groupId>
  4. <artifactId>spring-boot-starter</artifactId>
  5. </dependency>
  6.  
  7. <dependency>
  8. <groupId>org.springframework.boot</groupId>
  9. <artifactId>spring-boot-starter-test</artifactId>
  10. <scope>test</scope>
  11. </dependency>
  12. </dependencies>

1.2 启动类,添加启动注解

在springboot入口或者配置类中增加@EnableScheduling注解即可启用定时任务。

  1. @EnableScheduling
  2. @SpringBootApplication
  3. public class ScheduleApplication {
  4. public static void main(String[] args) {
  5. SpringApplication.run(ScheduleApplication.class, args);
  6. }
  7. }

  

1.3.添加定时任务

我们将对Spring Schedule三种任务调度器分别举例说明。

1.3.1 Cron表达式

类似于Linux下的Cron表达式时间定义规则。Cron表达式由6或7个空格分隔的时间字段组成,如下图:

常用表达式:

举个栗子:

添加一个work()方法,每10秒执行一次。

注意:当方法的执行时间超过任务调度频率时,调度器会在下个周期执行。

如:假设work()方法在第0秒开始执行,方法执行了12秒,那么下一次执行work()方法的时间是第20秒。

  1. @Component
  2. public class MyTask {
  3. @Scheduled(cron = "0/10 * * * * *")
  4. public void work() {
  5. // task execution logic
  6. }
  7. }

1.3.2 固定间隔任务

下一次的任务执行时间,是从方法最后一次任务执行结束时间开始计算。并以此规则开始周期性的执行任务。

举个栗子:

添加一个work()方法,每隔10秒执行一次。

例如:假设work()方法在第0秒开始执行,方法执行了12秒,那么下一次执行work()方法的时间是第22秒。

  1. @Scheduled(fixedDelay = 1000*10)
  2. public void work() {
  3. // task execution logic
  4. }

  

1.3.3 固定频率任务

按照指定频率执行任务,并以此规则开始周期性的执行调度。

举个栗子:

添加一个work()方法,每10秒执行一次。

注意:当方法的执行时间超过任务调度频率时,调度器会在当前方法执行完成后立即执行下次任务。

例如:假设work()方法在第0秒开始执行,方法执行了12秒,那么下一次执行work()方法的时间是第12秒。

  1. @Scheduled(fixedRate = 1000*10)
  2. public void work() {
  3. // task execution logic
  4. }

2、配置TaskScheduler线程池


在实际项目中,我们一个系统可能会定义多个定时任务。那么多个定时任务之间是可以相互独立且可以并行执行的。

通过查看org.springframework.scheduling.config.ScheduledTaskRegistrar源代码,发现spring默认会创建一个单线程池。这样对于我们的多任务调度可能会是致命的,当多个任务并发(或需要在同一时间)执行时,任务调度器就会出现时间漂移,任务执行时间将不确定。

  1. protected void scheduleTasks() {
  2. if (this.taskScheduler == null) {
  3. this.localExecutor = Executors.newSingleThreadScheduledExecutor();
  4. this.taskScheduler = new ConcurrentTaskScheduler(this.localExecutor);
  5. }
  6. //省略...
  7. }

  

2.1 自定义线程池

新增一个配置类,实现SchedulingConfigurer接口。重写configureTasks方法,通过taskRegistrar设置自定义线程池。

  1. @Configuration
  2. public class ScheduleConfig implements SchedulingConfigurer {
  3. @Override
  4. public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
  5. taskRegistrar.setScheduler(taskExecutor());
  6. }
  7.  
  8. @Bean(destroyMethod="shutdown")
  9. public Executor taskExecutor() {
  10. return Executors.newScheduledThreadPool(20);
  11. }
  12. }

3、实际应用中的问题


3.1 Web应用中的启动和关闭问题

我们知道通过spring加载或初始化的Bean,在服务停止的时候,spring会自动卸载(销毁)。但是由于线程是JVM级别的,如果用户在Web应用中启动了一个线程,那么这个线程的生命周期并不会和Web应用保持一致。也就是说,即使Web应用停止了,这个线程依然没有结束(死亡)。

解决方法:

1)当前对象是通过spring初始化

spring在卸载(销毁)实例时,会调用实例的destroy方法。通过实现DisposableBean接口覆盖destroy方法实现。在destroy方法中主动关闭线程。

  1. @Component
  2. public class MyTask implements DisposableBean{
  3. @Override
  4. public void destroy() throws Exception {
  5. //关闭线程或线程池
  6. ThreadPoolTaskScheduler scheduler = (ThreadPoolTaskScheduler)applicationContext.getBean("scheduler");
  7. scheduler.shutdown();
  8. }
  9. //省略...
  10. }

 

2)当前对象不是通过spring初始化(管理)

那么我们可以增加一个Servlet上下文监听器,在Servlet服务停止的时候主动关闭线程。

  1. public class MyTaskListenter implements ServletContextListener{
  2. @Override
  3. public void contextDestroyed(ServletContextEvent arg0) {
  4. //关闭线程或线程池
  5. }
  6. //省略...
  7. }

3.2 分布式部署问题

在实际项目中,我们的系统通常会做集群、分布式或灾备部署。那么定时任务就可能出现并发问题,即同一个任务在多个服务器上同时在运行。

解决方法(分布式锁):

1)通过数据库表锁

2)通过缓存中间件

3)通过Zookeeper实现

总结:

spring schedule给我们提供了一套简单、快速、高效、稳定的定时任务框架。但需要考虑线程的生命周期及分布式部署问题。


如果您觉得这篇文章对您有帮助,还望您在右下角帮忙点个“推荐”,博主感激不尽!

我的博客即将搬运同步至腾讯云+社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=2f0jbovt0mtcs

springboot集成schedule(深度理解)的更多相关文章

  1. springboot集成schedule

    背景 在项目开发过程中,我们经常需要执行具有周期性的任务.通过定时任务可以很好的帮助我们实现. 我们拿常用的几种定时任务框架做一个比较: 从以上表格可以看出,Spring Schedule框架功能完善 ...

  2. springBoot 集成Mysql数据库

    springBoot 集成Mysql数据库 前一段时间,我们大体介绍过SpringBoot,想必大家还有依稀的印象.我们先来回顾一下:SpringBoot是目前java世界最流行的一个企业级解决方案框 ...

  3. Springboot单元测试Junit深度实践

    Springboot单元测试Junit深度实践 前言 单元测试的好处估计大家也都知道了,但是大家可以发现在国内IT公司中真正推行单测的很少很少,一些大厂大部分也只是在核心产品推广单测来保障质量,今天这 ...

  4. Springboot集成cache的key生成策略

    代码接上文:深度理解springboot集成redis缓存之源码解析 ## 1.使用SpEL表达式 @Cacheable(cacheNames = "emp",key = &quo ...

  5. springboot集成Actuator

    Actuator监控端点,主要用来监控与管理. 原生端点主要分为三大类:应用配置类.度量指标类.操作控制类. 应用配置类:获取应用程序中加载的配置.环境变量.自动化配置报告等与SpringBoot应用 ...

  6. SpringBoot集成PageHelper时出现“在系统中发现了多个分页插件,请检查系统配置!”

    近日在项目中使用SpringBoot集成PageHelper后,跑单元测试时出现了"在系统中发现了多个分页插件,请检查系统配置!"这个问题. 如下图所示: org.mybatis. ...

  7. SpringBoot集成Spring Security入门体验

    一.前言 Spring Security 和 Apache Shiro 都是安全框架,为Java应用程序提供身份认证和授权. 二者区别 Spring Security:重量级安全框架 Apache S ...

  8. SpringBoot集成Spring Security(7)——认证流程

    文章目录 一.认证流程 二.多个请求共享认证信息 三.获取用户认证信息 在前面的六章中,介绍了 Spring Security 的基础使用,在继续深入向下的学习前,有必要理解清楚 Spring Sec ...

  9. SpringBoot集成Spring Security(6)——登录管理

    文章目录 一.自定义认证成功.失败处理 1.1 CustomAuthenticationSuccessHandler 1.2 CustomAuthenticationFailureHandler 1. ...

随机推荐

  1. RHEL6非交互式工具sshpass和expect安装

    RHEL6非交互式工具sshpass和expect安装 1 sshpass 在rhel6.4上,没有sshpass的软件包,无法采用yum方式安装.从源码编译安装非常简单. 1) 下载sshpass源 ...

  2. Android的Context Manager(服务管理器)源码剖析-android学习之旅(99)

    Context Manager介绍 Context Manager对应的进程是servicemanager进程,它先于Service Server和服务客户端运行,进入接收IPC数据的待机状态,处理来 ...

  3. TCP的基本概念三次握手,四次挥手

    TCP的特性 TCP提供一种面向连接的.可靠的字节流服务 在一个TCP连接中,仅有两方进行彼此通信.广播和多播不能用于TCP TCP使用校验和,确认和重传机制来保证可靠传输 TCP使用累积确认 TCP ...

  4. 杭电ACM 1001题

    import java.util.Scanner; public class Main { public static void main(String[] args) { Scanner sc=ne ...

  5. ubuntu下无法编译ruby-2.1.5提示something wrong with CFLAGS -arch x86_64

    在Mac OS X10.10下以下语句运行没有问题: ./configure -prefix=/Users/apple/src/ruby_src/ruby2.1.5_installed --with- ...

  6. css3-------:before和:after的作用

    1.:before和:after的作用就是在指定的元素内容(而不是元素本身)之前或者之后插入一个包含content属性指定内容的行内元素,最基本的用法如下: <!doctype html> ...

  7. 2.3MySQL 自带工具使用介绍

    1.mysql 首先看看“-e, --execute=name”参数,这个参数是告诉mysql,我只要执行“-e”后面的某个命令,而不是要通过mysql 连接登录到MySQL Server 上面.此参 ...

  8. IT小团队的管理者的突围之道

    笔者前几天被问到一个问题,你在团队管理方面有什么值得分享的吗?咋一听,实用千言万语,但是事后回忆说出来的东西感觉空无一物,缺少干货.故想通过写一篇随笔思考整理一下,刷新一下自己对小团队管理的认知.这里 ...

  9. YUV420格式解析

    一般的的YUV420图像格式实际上是Y'UV,420指的是其在Y U V上面的采样率.在YUV420的格式中,首先存储每一个像素的Y'值,然后跟着存储的是每2*2方阵采样一次的U值,最后存储的是每2* ...

  10. win7 64位专业版下的x64编译问题

    在Django的开发过程中,碰到一个问题,就是所有本地库的位数必须是相同的,于是某些库需要重新编译一下,工作环境,不能用盗版程序,VC++ 2008\2010 Express版本身都不支持X64的编译 ...