Spring自带定时任务@Scheduled注解

文章目录

  • 1. cron表达式生成器
  • 2. 简单定时任务代码示例:每隔两秒打印一次字符
  • 3. @Scheduled注解的参数
    • 3.1 cron
    • 3.2 fixedDelay
    • 3.3 fixedRate
    • 3.4 initialDelay
    • 3.5 fixedDelayString、fixedRateString、initialDelayString等是String类型,支持占位符
    • 3.6 timeUnit
  • 4. 问题:定时器的任务默认是按照顺序执行的,可能导致一些任务无法执行
    • 4.1 ScheduledAnnotationBeanPostProcessor类处理器解析带有@Scheduled注解的方法
    • 4.2 processScheduled方法处理@Scheduled注解后面的参数,并将其添加到任务列表中
    • 4.3 执行任务。ScheduledTaskRegistrar类为Spring容器的定时任务注册中心。Spring容器通过线程处理注册的定时任务
  • 5. 问题:当系统时间发生改变时,@Scheduled注解失效

1. cron表达式生成器

cron表达式生成器:https://cron.qqe2.com/

2. 简单定时任务代码示例:每隔两秒打印一次字符

import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;@Service
@EnableScheduling
public class ScheduleDemo1 {@Scheduled(cron = "*/2 * * * * ?")public static void test() {// 十六进制转换为字符System.out.print((char) Integer.parseInt("5fc3", 16));System.out.print((char) Integer.parseInt("6d41", 16));System.out.print((char) Integer.parseInt("65f6", 16));System.out.print((char) Integer.parseInt("95f4", 16));System.out.println();}
}

输出:
在这里插入图片描述

3. @Scheduled注解的参数

在这里插入图片描述

3.1 cron

参数接收一个cron表达式,cron表达式是一个以空格为间隔符来区分不同域的字符串,总共有6个或7个域。cron表达式从左到右每个域分别标识的[秒] [分] [小时] [日] [月] [周] [年],其中[年]不是必选的域可以省略。

序号必填值的范围允许的通配符
10-59, - * /
20-59, - * /
30-23, - * /
41-31, - * ? / L W
51-12 / JAN-DEC, - * /
61-7 or SUN-SAT, - * ? / L #
71970-2099, - * /

通配符说明:

  • * 表示所有值,例如:在时的字段上设置 *,表示每一个小时都会触发。
  • ? 表示不指定值,即当前使用的场景为不需要关心这个字段设置的值。例如:要在每月的10号触发一个操作,但不关心是周几,所以需要周位置的那个字段设置为“?”, 具体设置为 0 0 0 10 * ? 。
  • - 表示区间,例如:在小时上设置 “10-12”,表示 10,11,12点都会触发。
  • , 表示指定多个值,例如在周字段上设置 “MON,WED,FRI” 表示周一,周三和周五触发。
  • / 用于递增触发,如在秒上面设置“5/15” 表示从5秒开始,每隔15秒触发(5,20,35,50)。在日字段上设置‘1/3’所示每月1号开始,每隔三天触发一次
  • L 表示最后的意思,在日字段设置上,表示当月的最后一天(依据当前月份,如果是二月还会依据是否是闰年), 在周字段上表示星期六,相当于“7”或“SAT”。如果在“L”前加上数字,则表示该数据的最后一个。例如在周字段上设置“6L”这样的格式,则表示“本月最后一个星期五”。
  • W表示离指定日期的最近那个工作日(周一至周五)。例如在日字段上置“15W”,表示离每月15号最近的那个工作日触发。如果15号正好是周六,则找最近的周五(14号)触发, 如果15号是周未,则找最近的下周一(16号)触发。如果15号正好在工作日(周一至周五),则就在该天触发。如果指定格式为 “1W”,它则表示每月1号往后最近的工作日触发。如果1号正是周六,则将在3号下周一触发。(注,“W”前只能设置具体的数字,不允许区间“-”)。
  • #序号(表示每月的第几个周几),例如在周字段上设置“6#3”表示在每月的第三个周六。注意如果指定“#5”,正好第五周没有周六,则不会触发该配置;小提示:‘L’和 ‘W’可以一组合使用。如果在日字段上设置“LW”,则表示在本月的最后一个工作日触发;周字段的设置,若使用英文字母是不区分大小写的,即MON与mon相同。

示例:

  • 每隔5秒执行一次:*/5 * * * * ?

  • 每隔1分钟执行一次:0 */1 * * * ?

  • 每天23点执行一次:0 0 23 * * ?

  • 每天凌晨1点执行一次:0 0 1 * * ?

  • 每月1号凌晨1点执行一次:0 0 1 1 * ?

3.2 fixedDelay

上一次执行完成后延迟多久执行下一次,以上一次任务执行的完成时间开始延迟,如:

@Scheduled(fixedDelay = 5000) //上一次执行完成后延迟5s再执行

3.3 fixedRate

固定延迟多久执行下一次任务,不依赖于上一次任务执行成功的时间,如:

@Scheduled(fixedRate= 5000) //上一次执行后延迟5s就开始执行

3.4 initialDelay

启动后延迟多久后执行第一次,可根据场景搭配fixedRate或fixedDelay实现定时调度,如:

@Scheduled(initialDelay = 5000,fixedRate= 300000) //启动后延迟5s执行,之后每次执行时间间隔5min

3.5 fixedDelayString、fixedRateString、initialDelayString等是String类型,支持占位符

如:@Scheduled(fixedDelayString = “${task.fixed-delay}”)

3.6 timeUnit

时间单位,默认毫秒

TimeUnit timeUnit() default TimeUnit.MILLISECONDS;

4. 问题:定时器的任务默认是按照顺序执行的,可能导致一些任务无法执行

我创建定时器执行任务目的是为了让它多线程执行任务,但是后来才发现,@Scheduled注解的方法默认是按照顺序执行的,这会导致当一个任务挂死的情况下,其它任务都在等待,无法执行。

@Scheduled注解加载的过程,以及它是如何执行的:
在这里插入图片描述

4.1 ScheduledAnnotationBeanPostProcessor类处理器解析带有@Scheduled注解的方法

在这里插入图片描述

4.2 processScheduled方法处理@Scheduled注解后面的参数,并将其添加到任务列表中

在这里插入图片描述

4.3 执行任务。ScheduledTaskRegistrar类为Spring容器的定时任务注册中心。Spring容器通过线程处理注册的定时任务

首先,调用scheduleCronTask初始化定时任务。

在这里插入图片描述
然后,在ThreadPoolTaskScheduler类中,会对线程池进行初始化,线程池的核心线程数量为1,

private volatile int poolSize = 1;

在这里插入图片描述
阻塞队列为DelayedWorkQueue。

在这里插入图片描述
因此,原因就找到了,当有多个方法使用@Scheduled注解时,就会创建多个定时任务到任务列表中,当其中一个任务没执行完时,其它任务在阻塞队列当中等待,因此,所有的任务都是按照顺序执行的,只不过由于任务执行的速度相当快,让我们感觉任务都是多线程执行的。

下面举例来验证一下,将上述的某个定时任务添加睡眠时间,观察另一个定时任务是否输出。

import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;import java.util.concurrent.TimeUnit;@Slf4j
@EnableScheduling
@Component
public class ScheduleDemo2 {private static final ThreadLocal<Integer> threadLocalA = new ThreadLocal<>();@Scheduled(cron = "0/2 * * * * ?")public void taskA() {try {log.info("执行了ScheduleTask类中的taskA方法");Thread.sleep(TimeUnit.SECONDS.toMillis(10));} catch (InterruptedException e) {e.printStackTrace();}}@Scheduled(cron = "0/1 * * * * ?")public void taskB() {int num = threadLocalA.get() == null ? 0 : threadLocalA.get();![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/2022840e83f049e2875381196e7c55ea.png)log.info("taskB方法执行次数:{}", ++num);threadLocalA.set(num);}
}

输出:可以观察到两个定时任务不是同时执行的,是按顺序执行的

在这里插入图片描述
想要避免顺序执行,进行并发,就要配置定时任务线程池:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledThreadPoolExecutor;@Configuration
public class ScheduleConfig implements SchedulingConfigurer {@Overridepublic void configureTasks(ScheduledTaskRegistrar taskRegistrar) {taskRegistrar.setScheduler(getExecutor());}@Beanpublic Executor getExecutor(){return new ScheduledThreadPoolExecutor(5);}
}

输出:可以观察到两个定时任务不是顺序执行了,从出现次数的乱序这种多线程问题也可以看出是并发执行了

在这里插入图片描述
从输出结果我们可以看到,即使testA休眠,但是testB仍然正常执行,并且其还复用了其它线程,导致执行次数发生了变化。

5. 问题:当系统时间发生改变时,@Scheduled注解失效

另外一种情况就是在配置完线程池之后,当你手动修改服务器时间时,目前我做的测试就是服务器时间调前,则会导致注解失效,而服务器时间调后,则不会影响注解的作用。

原因:

JVM启动之后会记录当前系统时间,然后JVM根据CPU ticks自己来算时间,此时获取的是定时任务的基准时间。如果此时将系统时间进行了修改,当Spring将之前获取的基准时间与当下获取的系统时间进行比对不一致,就会造成Spring内部定时任务失效。因为此时系统时间发生变化了,不会触发定时任务。

解决办法:

  1. 重启项目

  2. 不使用@Scheduled注解,改成ScheduledThreadPoolExecutor进行替代,部分代码:在这里插入图片描述

实际项目中一般使用xxl-job、Quartz等框架,@Scheduled注解会使用的话也是定时更新一些变量的值,大量的定时任务还是使用专门的定时任务框架实现

参考1
参考2
参考3

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/22993.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

GD32F407ZGT6/GD32F450ZGT6(3)外部中断实验

本文章基于兆易创新GD32 MCU所提供的2.2.4版本库函数开发 向上代码兼容GD32F450ZGT6中使用 后续项目主要在下面该专栏中发布&#xff1a; https://blog.csdn.net/qq_62316532/category_12608431.html?spm1001.2014.3001.5482 感兴趣的点个关注收藏一下吧! 电机驱动开发可以跳转…

用幻灯片讲解C++手动内存管理

用幻灯片讲解C手动内存管理 1.栈内存的基本元素 2.栈内存的聚合对象 3.手动分配内存和释放内存 注意&#xff1a;手动分配内存&#xff0c;指的是在堆内存中。 除非实现自己的数据结构&#xff0c;否则永远不要手动分配内存! 即使这样&#xff0c;您也应该通过std::allocator…

进入新公司有焦虑感怎么办?

前因 前两天技术交流群里有童鞋问了一个很有意思的问题&#xff0c;他问如何克服进入新公司的焦虑感&#xff1f;很多热心的童鞋都纷纷支招&#xff0c;比如 “主动干活”、“专注干活”、“让时间冲淡焦虑感”、……等等&#xff0c;这些都很有道理&#xff0c;不过&#xff…

今时今日蜘蛛池还有用吗?

最近不知道哪里又开始刮起“蜘蛛池”这个风气了&#xff0c;售卖、购买蜘蛛池的行为又开始在新手站长圈里开始蔓延和流行了起来&#xff0c;乍一看到“蜘蛛池”这个词给明月的感受就是陌生&#xff0c;要经过回忆才能想起来一些残存的记忆&#xff0c;所谓的蜘蛛池说白了就是利…

grpc接口调用

grpc接口调用 准备依赖包clientserver 参考博客&#xff1a; Grpc项目集成到java方式调用实践 gRpc入门和springboot整合 java 中使用grpc java调用grpc服务 准备 因为需要生成代码&#xff0c;所以必备插件 安装后重启 依赖包 <?xml version"1.0" encoding&…

mysql buffer pool 详解

概念&#xff1a;为了缓存磁盘中的页&#xff0c;mysql服务器启动时会向操作系统申请一片连续的内存空间&#xff0c;这片连续的内存空间叫做buffer pool&#xff0c;即缓冲池。 buffer pool 默认大小&#xff1a;128M innodb_buffer_pool_size&#xff1a;自定义缓冲池大小 …

ECS搭建redis4.0集群版

在 CentOS 上安装 Redis 4.0 集群版涉及多个步骤&#xff0c;包括安装 Redis、配置集群并启动它。下面将详细介绍整个过程&#xff1a; 1. 系统更新 首先&#xff0c;保证系统是最新的。 sudo yum update2. 安装依赖项 安装构建 Redis 所必需的依赖&#xff1a; sudo yum …

计算机三级等级考试

计算机等级考试&#xff1a; 一&#xff1a;理论知识考试 100分考60分 1&#xff1a;题库 二&#xff1a;技能考试 100分考60分 1&#xff1a;写文档 项目概述 功能描述 数据库设计 UML 绘 图 用例图 与 包图&#xff08;两个图&#xff09; 2&…

node mysql的增删改查基础

学习koa时&#xff0c;不选择mongodb&#xff0c;而是MySQL&#xff0c;虽然node对mongodb更亲和&#xff0c;但是我感觉MySQL的键值对的储存结构更正规 1.首选确认你的数据库有个库。有个表,我的如下 2.配置 let mySqlConfig{host:localhost,user:root,password:123456,data…

C#操作MySQL从入门到精通(10)——对查询数据进行通配符过滤

前言 我们有时候需要查询数据,并且这个数据包含某个字符串,这时候我们再使用where就无法实现了,所以mysql中提供了一种模糊查询机制,通过Like关键字来实现,下面进行详细介绍: 本次查询的表中数据如下: 1、使用(%)通配符 %通配符的作用是,表示任意字符出现任意次数…

2024.6.5

1、react原理学习&#xff0c; hook、fiber 2、瀑布流组件完善 3、代码随想录二刷

如何充分利用代理IP扩大网络接触面

目录 前言 第一部分&#xff1a;什么是代理IP&#xff1f; 第二部分&#xff1a;如何获取代理IP&#xff1f; 1. IP质量 2. 匿名性 3. 限制 第三部分&#xff1a;如何使用代理IP&#xff1f; 第四部分&#xff1a;如何充分利用代理IP&#xff1f; 总结&#xff1a; 前…

【Python数据预处理系列】Pandas 数据操作实战:掌握 .loc[] 方法进行高效数据选取

文章将详细介绍.loc[]方法的各种使用场景&#xff0c;帮助读者深入理解并掌握这一核心功能。 在Pandas库中&#xff0c;.loc[]方法是一种强大而灵活的数据选取工具。本文将通过详细的步骤和示例&#xff0c;手把手教您如何利用这一工具进行高效的数据操作。 首先&#xff0c;我…

掌握SVG基础:从零开始学习

格栅图可以实现图片的清晰显示&#xff0c;但这也意味着如果要在各种设备上使用格栅图&#xff0c;就会增加大量不同规格的格栅图&#xff0c;以适应各种尺寸的设备。这也直接导致资源文件体积的增加&#xff0c;矢量图没有这个问题。本文将SVG代码编写与即时设计工具相结合&am…

C++ Primer 总结索引 | 第十五章:面向对象程序设计

继承和动态绑定 对程序的编写 有两方面的影响&#xff1a;一是 我们可以更容易地定义与其他类相似 但不完全相同的新类&#xff1b;二是 在使用这些彼此相似的类编写程序时&#xff0c;我们可以在一定程度上 忽略掉它们的区别 在很多程序中都存在着一些相互关联 但是有细微差别…

PDF批量加水印 与 去除水印实践

本文主要目标是尝试去除水印&#xff0c;但是为了准备测试数据&#xff0c;我们需要先准备好有水印的pdf测试文件。 注意&#xff1a;本文的去水印只针对文字悬浮图片悬浮两种特殊情况&#xff0c;即使是这两种情况也不代表一定都可以去除水印。 文章目录 批量添加透明图片水印…

【Web API DOM10】日期(时间)对象

一&#xff1a;实例化 1 获取系统当前时间即创建日期对象 const date new Date() console.log(date) 2024年6月5日周三 2 获取指定的时间 以获取2025年6月29日为例 const date new Date(2025-6-29) console.log(date) 二&#xff1a;日期对象方法 1 使用场景&#xf…

关于信号翻转模块(sig_flag_mod)的实现

关于信号翻转模块(sig_flag_mod)的实现 语言 &#xff1a;Verilg HDL 、VHDL EDA工具&#xff1a;ISE、Vivado、Quartus II 关于信号翻转模块(sig_flag_mod)的实现一、引言二、实现信号翻转模块的方法&#xff08;1&#xff09;输入接口&#xff08;2&#xff09;输出接口&…

新手学习编程网站一站式合集

LTPP在线开发平台 探索编程世界的新天地&#xff0c;为学生和开发者精心打造的编程平台&#xff0c;现已盛大开启&#xff01;这个平台汇集了近4000道精心设计的编程题目&#xff0c;覆盖了C、C、JavaScript、TypeScript、Go、Rust、PHP、Java、Ruby、Python3以及C#等众多编程语…

【javaEE初阶】

&#x1f308;&#x1f308;&#x1f308;关于java ⚡⚡⚡java的由来 我们这篇文章主要是来介绍javaEE&#xff0c;一般称为java企业版&#xff0c;实际上java的历史可以追溯到上个世纪90年代&#xff0c;当时主要的语言主流的还是C语言和C&#xff0c;但是在那个时期嵌入式初…