Spring自带定时任务@Scheduled注解

news2024/11/19 23:35:26

文章目录

  • 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 {

    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        taskRegistrar.setScheduler(getExecutor());
    }

    @Bean
    public 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.coloradmin.cn/o/1791640.html

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

相关文章

SEO 与 PPC 之间的区别

按点击付费 &#xff08;PPC&#xff09;&#xff1a; PPC 是一种网络营销技术&#xff0c;广告商在每次点击广告时向网站支付一定金额&#xff0c;广告商只为符合条件的点击付费。Google 广告、Bing 和 Yahoo 广告基于按点击付费的概念。PPC是用于在搜索引擎首页上列出的最快方…

LeetCode刷题之HOT100之合并区间

雨下了一整天&#xff0c;中午早早就回去吃饭拿快递了&#xff0c;今天拿了很多快递。我的书回来啦哈哈&#xff0c;还有好多零食&#xff0c;爽歪歪啊&#xff0c;放在下面了&#xff0c;然后准备开始做题啦&#xff01; 图一&#xff1a;左一是xh送我的&#xff0c;非常精彩…

测试基础09:缺陷(bug)生命周期、定位方式和管理规范

课程大纲 1、缺陷&#xff08;bug&#xff09;生命周期 2、缺陷&#xff08;bug&#xff09;提交规范 2.1 宗旨 简洁、清晰、可视化&#xff0c;减少沟通成本。 2.2 bug格式和内容 ① 标题&#xff1a;一级功能-二级功能-三级功能_&#xff08;一句话描述bug&#xff1a;&…

什么是证书吊销?面对证书吊销,应采取什么措施?

SSL证书是网络安全的重要组成部分&#xff0c;它确保了网站与用户之间的信息传输安全。然而&#xff0c;SSL证书也可能因为某些原因被吊销&#xff0c;这就需要我们了解证书吊销的概念、原因以及应对措施。 证书吊销的定义 证书吊销是指已经签发的SSL证书被证书颁发机构&…

【JVM】已验鼎真,鉴定为:妈妈加载的(双亲委派模型)

【JVM】已验鼎真&#xff0c;鉴定为&#xff1a;妈妈加载的&#xff08;双亲委派模型&#xff09; 在Java的世界中&#xff0c;类加载器&#xff08;ClassLoader&#xff09;是Java虚拟机&#xff08;JVM&#xff09;用来动态加载类的基础组件。双亲委派模型&#xff08;Paren…

java多线程原理

1.线程创建与启动&#xff1a;通过继承Thread类或实现Runnable接口创建线程&#xff0c;并调用start()方法启动线程。 1.线程状态&#xff1a;线程在其生命周期中有多种状态&#xff0c;包括新建、运行、阻塞、死亡等。了解这些状态以及如何在它们之间转换对于管理线程至关重要…

人工智能在病理组学中的发展历程概述|24年6月·顶刊速递·06-04

小罗碎碎念 今天分享的七篇文章&#xff0c;主题非常明确——人工智能在数字病理领域的发展情况。 前六篇文献选自Nature&#xff0c;第七篇文献选自Cell&#xff0c;文献的发表时间从2019年跨越至2024年&#xff0c;囊括了AI在数字病理领域的里程碑式进展。 这期推文值得所有…

输出流--6.6

代码以及解释&#xff1a; package java2;import java.io.File; import java.io.FileWriter; import java.io.IOException;public class Test02 {public static void main(String[] args) throws IOException {String str "flag{hello_ctf}";//1.封装File fnew Fil…

代码解读 | Hybrid Transformers for Music Source Separation[02]

一、背景 今天对Hybrid Transformer Demucs代码进行解读&#xff0c;目标&#xff1a;对图c中各个模块对应的代码进行解读&#xff08;时域编码、频域编码、时域解码、频域解码、Cross-Domain Transformer Encoder模块、STFT模块、ISTFT模块&#xff09;。解读的代码是开源工程…

深入解析ArrayList是如何实现自动扩容的?【源码深度解析】

一 、分析ArrayList扩容源码 通过在 &#xff08;JDK 6 及更低版本中&#xff09;API我们知道&#xff0c;调用无参构造创建的ArrayList集合&#xff0c;初始容量为10 接下来我们深入源码&#xff0c;探究当时作者是怎么构思的 借助Debug工具&#xff0c;一步一步进入 上图看到…

list的简单模拟实现

文章目录 目录 文章目录 前言 一、使用list时的注意事项 1.list不支持std库中的sort排序 2.去重操作 3.splice拼接 二、list的接口实现 1.源码中的节点 2.源码中的构造函数 3.哨兵位头节点 4.尾插和头插 5.迭代器* 5.1 迭代器中的operator和-- 5.2其他迭代器中的接口 5.3迭代器…

web学习笔记(六十二)

目录 1.键盘事件 2.KeepAlive 3.组件传值 3.1 兄弟组件传值 3.2 组件树传值 3.3 发布订阅者传值 1.键盘事件 keydown表示键盘事件&#xff0c;在不加修饰符的情况下&#xff0c;点击键盘上的任意位置都可以触发键盘事件&#xff0c; <template><div><!--…

LeetCode-82. 删除排序链表中的重复元素 II【链表 双指针】

LeetCode-82. 删除排序链表中的重复元素 II【链表 双指针】 题目描述&#xff1a;解题思路一&#xff1a;用一个cur即可实现去重cur.next cur.next.next背诵版&#xff1a;解题思路三&#xff1a;0 题目描述&#xff1a; 给定一个已排序的链表的头 head &#xff0c; 删除原始…

HTML静态网页成品作业(HTML+CSS)—— 父亲节节日介绍网页(4个页面)

&#x1f389;不定期分享源码&#xff0c;关注不丢失哦 文章目录 一、作品介绍二、作品演示三、代码目录四、网站代码HTML部分代码 五、源码获取 一、作品介绍 &#x1f3f7;️本套采用HTMLCSS&#xff0c;未使用Javacsript代码&#xff0c;共有4个页面。 二、作品演示 三、代…

【学习心得】算法刷题心得分享

一、为什么要刷题&#xff1f; 提升编程能力&#xff0c;强化对数据结构的理解&#xff0c;熟练掌握常用的算法等为竞赛、考试做准备找实习、找工作需要&#xff08;上机考试面试手撕代码&#xff09;提升自信心&#xff0c;放松一下 二、刷题前应该有哪些知识储备&#xff1f;…

java代码审计之fastjson反序列化漏洞

fastjson反序列化漏洞分析 Fastjson 是一个 Java 库&#xff0c;可以将 Java 对象转换为 JSON 格式&#xff0c;当然它也可以将 JSON 字符串转换为 Java 对象。Fastjson 可以操作任何 Java 对象&#xff0c;即使是一些预先存在的没有源码的对象。该产品主要提供了两个接口&…

官宣!2024 MongoDB Developer Day来了!北上深三场等你集结!

北上深开发者 专为你们打造的 MongoDB Developer Day 来了&#xff01; 动手实操工作坊➕模型设计优化专场 学习 NoSQL 数据建模的最佳实践 深入探索 MongoDB 的各种可能性 和开发者同行和 MongoDB 技术专家 一起度过充实的一天&#xff01; 北京&#xff08;6/22&…

UMLChina为什么叒要翻译《分析模式》?

UMLChina受机械工业出版社委托&#xff0c;重新翻译《分析模式》。 Martin Fowler的“Analysis Patterns&#xff0c;Reusable Object Models”&#xff0c;原书出版于1997年&#xff0c;至今为止未出第2版。 2004年&#xff0c;机械工业出版社出版该书中译本《分析模式》。 …

Django render()函数页面渲染

1&#xff0c; render() 函数 在Django框架中&#xff0c;render() 函数是一个非常有用的快捷方式&#xff0c;用于从视图函数返回一个完整的HTTP响应。它负责将给定的模板与上下文数据结合&#xff0c;渲染出最终的HTML页面&#xff0c;并返回一个HttpResponse对象。 from d…

千锋教育大优惠

IT全学科自学至尊卡&#xff08;3年卡&#xff09; Linux云计算运维、Python全栈、数据分析、人工智能、Java、大前端、网络安全、物联网、全媒体、影视剪辑等14大主流方向&#xff0c;300精品视频课程免费学。课程持续更新&#xff0c;电脑端手机APP小程序多平台无忧畅学&…