SpringBoot定时任务@Scheduled注解详解

SpringBoot定时任务@Scheduled注解详解

SpringBoot定时任务@Scheduled注解详解

  • 项目开发中,经常会遇到定时任务的场景,Spring提供了@Scheduled注解,方便进行定时任务的开发

概述

  • 要使用@Scheduled注解,首先需要在启动类添加@EnableScheduling,启用Spring的计划任务执行功能,这样可以在容器中的任何Spring管理的bean上检测@Scheduled注解,执行计划任务

注解定义

  • @Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Repeatable(Schedules.class)
    public @interface Scheduled {
    ​
        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 "";
    ​
    }
    ​

参数说明

  •  

源码解析

  • 配置了@Scheduled注解的方法,Spring的处理是通过注册ScheduledAnnotationBeanPostProcessor来执行,将不同配置参数的任务分配给不同的handler处理,核心代码如下

  • org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor#postProcessAfterInitialization

  • @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        if (bean instanceof AopInfrastructureBean || bean instanceof TaskScheduler ||
                bean instanceof ScheduledExecutorService) {
            // Ignore AOP infrastructure such as scoped proxies.
            return bean;
        }
    ​
        Class<?> targetClass = AopProxyUtils.ultimateTargetClass(bean);
        if (!this.nonAnnotatedClasses.contains(targetClass) &&
                AnnotationUtils.isCandidateClass(targetClass, Arrays.asList(Scheduled.class, Schedules.class))) {
            Map<Method, Set<Scheduled>> annotatedMethods = MethodIntrospector.selectMethods(targetClass,
                    (MethodIntrospector.MetadataLookup<Set<Scheduled>>) method -> {
                        Set<Scheduled> scheduledMethods = AnnotatedElementUtils.getMergedRepeatableAnnotations(
                                method, Scheduled.class, Schedules.class);
                        return (!scheduledMethods.isEmpty() ? scheduledMethods : null);
                    });
            if (annotatedMethods.isEmpty()) {
                this.nonAnnotatedClasses.add(targetClass);
                if (logger.isTraceEnabled()) {
                    logger.trace("No @Scheduled annotations found on bean class: " + targetClass);
                }
            }
            else {
                // Non-empty set of methods
                annotatedMethods.forEach((method, scheduledMethods) ->
                        scheduledMethods.forEach(scheduled -> processScheduled(scheduled, method, bean)));
                if (logger.isTraceEnabled()) {
                    logger.trace(annotatedMethods.size() + " @Scheduled methods processed on bean '" + beanName +
                            "': " + annotatedMethods);
                }
            }
        }
        return bean;
    }
    ​

  • org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor#processScheduled

  • /**
     * Process the given {@code @Scheduled} method declaration on the given bean.
     * @param scheduled the @Scheduled annotation
     * @param method the method that the annotation has been declared on
     * @param bean the target bean instance
     * @see #createRunnable(Object, Method)
     */
    protected void processScheduled(Scheduled scheduled, Method method, Object bean) {
        try {
            Runnable runnable = createRunnable(bean, method);
            boolean processedSchedule = false;
            String errorMessage =
                    "Exactly one of the 'cron', 'fixedDelay(String)', or 'fixedRate(String)' attributes is required";
            Set<ScheduledTask> tasks = new LinkedHashSet<>(4);
            // Determine initial delay
            long initialDelay = scheduled.initialDelay();
            String initialDelayString = scheduled.initialDelayString();
            if (StringUtils.hasText(initialDelayString)) {
                Assert.isTrue(initialDelay < 0, "Specify 'initialDelay' or 'initialDelayString', not both");
                if (this.embeddedValueResolver != null) {
                    initialDelayString = this.embeddedValueResolver.resolveStringValue(initialDelayString);
                }
                if (StringUtils.hasLength(initialDelayString)) {
                    try {
                        initialDelay = parseDelayAsLong(initialDelayString);
                    }
                    catch (RuntimeException ex) {
                        throw new IllegalArgumentException(
                                "Invalid initialDelayString value \"" + initialDelayString + "\" - cannot parse into long");
                    }
                }
            }
            // Check cron expression
            String cron = scheduled.cron();
            if (StringUtils.hasText(cron)) {
                String zone = scheduled.zone();
                if (this.embeddedValueResolver != null) {
                    cron = this.embeddedValueResolver.resolveStringValue(cron);
                    zone = this.embeddedValueResolver.resolveStringValue(zone);
                }
                if (StringUtils.hasLength(cron)) {
                    Assert.isTrue(initialDelay == -1, "'initialDelay' not supported for cron triggers");
                    processedSchedule = true;
                    if (!Scheduled.CRON_DISABLED.equals(cron)) {
                        TimeZone timeZone;
                        if (StringUtils.hasText(zone)) {
                            timeZone = StringUtils.parseTimeZoneString(zone);
                        }
                        else {
                            timeZone = TimeZone.getDefault();
                        }
                        tasks.add(this.registrar.scheduleCronTask(new CronTask(runnable, new CronTrigger(cron, timeZone))));
                    }
                }
            }
            // At this point we don't need to differentiate between initial delay set or not anymore
            if (initialDelay < 0) {
                initialDelay = 0;
            }
            // Check fixed delay
            long fixedDelay = scheduled.fixedDelay();
            if (fixedDelay >= 0) {
                Assert.isTrue(!processedSchedule, errorMessage);
                processedSchedule = true;
                tasks.add(this.registrar.scheduleFixedDelayTask(new FixedDelayTask(runnable, fixedDelay, initialDelay)));
            }
            String fixedDelayString = scheduled.fixedDelayString();
            if (StringUtils.hasText(fixedDelayString)) {
                if (this.embeddedValueResolver != null) {
                    fixedDelayString = this.embeddedValueResolver.resolveStringValue(fixedDelayString);
                }
                if (StringUtils.hasLength(fixedDelayString)) {
                    Assert.isTrue(!processedSchedule, errorMessage);
                    processedSchedule = true;
                    try {
                        fixedDelay = parseDelayAsLong(fixedDelayString);
                    }
                    catch (RuntimeException ex) {
                        throw new IllegalArgumentException(
                                "Invalid fixedDelayString value \"" + fixedDelayString + "\" - cannot parse into long");
                    }
                    tasks.add(this.registrar.scheduleFixedDelayTask(new FixedDelayTask(runnable, fixedDelay, initialDelay)));
                }
            }
            // Check fixed rate
            long fixedRate = scheduled.fixedRate();
            if (fixedRate >= 0) {
                Assert.isTrue(!processedSchedule, errorMessage);
                processedSchedule = true;
                tasks.add(this.registrar.scheduleFixedRateTask(new FixedRateTask(runnable, fixedRate, initialDelay)));
            }
            String fixedRateString = scheduled.fixedRateString();
            if (StringUtils.hasText(fixedRateString)) {
                if (this.embeddedValueResolver != null) {
                    fixedRateString = this.embeddedValueResolver.resolveStringValue(fixedRateString);
                }
                if (StringUtils.hasLength(fixedRateString)) {
                    Assert.isTrue(!processedSchedule, errorMessage);
                    processedSchedule = true;
                    try {
                        fixedRate = parseDelayAsLong(fixedRateString);
                    }
                    catch (RuntimeException ex) {
                        throw new IllegalArgumentException(
                                "Invalid fixedRateString value \"" + fixedRateString + "\" - cannot parse into long");
                    }
                    tasks.add(this.registrar.scheduleFixedRateTask(new FixedRateTask(runnable, fixedRate, initialDelay)));
                }
            }
            // Check whether we had any attribute set
            Assert.isTrue(processedSchedule, errorMessage);
            // Finally register the scheduled tasks
            synchronized (this.scheduledTasks) {
                Set<ScheduledTask> regTasks = this.scheduledTasks.computeIfAbsent(bean, key -> new LinkedHashSet<>(4));
                regTasks.addAll(tasks);
            }
        }
        catch (IllegalArgumentException ex) {
            throw new IllegalStateException(
                    "Encountered invalid @Scheduled method '" + method.getName() + "': " + ex.getMessage());
        }
    }
     
  • org.springframework.scheduling.config.ScheduledTaskRegistrar#scheduleTasks

  • /**
     * Schedule all registered tasks against the underlying
     * {@linkplain #setTaskScheduler(TaskScheduler) task scheduler}.
     */
    proected void scheduleTasks() {
        if (this.taskScheduler == null) {
            this.localExecutor = Executors.newSingleThreadScheduledExecutor();
            this.taskScheduler = new ConcurrentTaskScheduler(this.localExecutor);
        }
        if (this.triggerTasks != null) {
            for (TriggerTask task : this.triggerTasks) {
                addScheduledTask(scheduleTriggerTask(task));
            }
        }
        if (this.cronTasks != null) {
            for (CronTask task : this.cronTasks) {
                addScheduledTask(scheduleCronTask(task));
            }
        }
        if (this.fixedRateTasks != null) {
            for (IntervalTask task : this.fixedRateTasks) {
                addScheduledTask(scheduleFixedRateTask(task));
            }
        }
        if (this.fixedDelayTasks != null) {
            for (IntervalTask task : this.fixedDelayTasks) {
                addScheduledTask(scheduleFixedDelayTask(task));
            }
        }
    }

使用详解

定时任务同步/异步执行

  • 定时任务执行默认是单线程模式,会创建一个本地线程池,线程池大小为1。当项目中有多个定时任务时,任务之间会相互等待,同步执行

  • 源码:

  • // org.springframework.scheduling.config.ScheduledTaskRegistrar#scheduleTasks
    if (this.taskScheduler == null) {
        this.localExecutor = Executors.newSingleThreadScheduledExecutor();
        this.taskScheduler = new ConcurrentTaskScheduler(this.localExecutor);
    }
    ​
    // java.util.concurrent.Executors#newSingleThreadScheduledExecutor()
    public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
        return new DelegatedScheduledExecutorService
            (new ScheduledThreadPoolExecutor(1));
    }
  • 代码示例:

  • @Slf4j
    @Component
    public class RunIntervalTestScheduler {
    ​
        @Scheduled(cron = "0/1 * * * * ?")
        public void singleThreadTest1() {
            log.info("singleThreadTest1");
            LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1));
        }
    ​
        @Scheduled(cron = "0/1 * * * * ?")
        public void singleThreadTest2() {
            log.info("singleThreadTest2");
            LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1));
        }
    ​
        @Scheduled(cron = "0/1 * * * * ?")
        public void singleThreadTest3() {
            log.info("singleThreadTest3");
            LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1));
        }
    }
    ​

  • 执行结果:

  • 可以看到,默认情况下,三个任务串行执行,都使用pool-1-thread-1同一个线程池,并且线程只有一个

  • 可以通过实现SchedulingConfigurer接口,手动创建线程池,配置期望的线程数量

  • 示例代码:

  • @Configuration
    public class ScheduledConfig implements SchedulingConfigurer {
    ​
        /**
         * 任务执行线程池大小
         */
        private static final int TASK_POOL_SIZE = 50;
        /**
         * 线程名
         */
        private static final String TASK_THREAD_PREFIX = "test-task-";
    ​
        @Override
        public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
            ThreadPoolTaskScheduler taskPool = new ThreadPoolTaskScheduler();
            taskPool.setPoolSize(TASK_POOL_SIZE);
            taskPool.setThreadNamePrefix(TASK_THREAD_PREFIX);
            taskPool.initialize();
            scheduledTaskRegistrar.setTaskScheduler(taskPool);
        }
    }
    ​

  • 任务执行结果:

  • 此时任务的执行已经异步化,从自定义线程池中分配线程执行任务,在实际应用中需要考虑实际任务数量,创建相应大小的线程池

fixedRate/fixedDelay区别

  • fixedRate是配置上一次任务执行开始到下一次执行开始的间隔时间,不会等待上一次任务执行完成就会调度下一次任务,将其放入等待队列中

  • 代码示例:

  • @Slf4j
    @Component
    public class RunIntervalTestScheduler {
    ​
        @Scheduled(initialDelay = 1000, fixedRate = 1000)
        public void fixedRate() throws Exception {
            log.info("fixedRate run");
            TimeUnit.SECONDS.sleep(3);
        }
    ​
    }
  • 执行结果:

  • 任务配置的fixedRate为1s,执行日志打印的时间间隔都是3s左右,也就是上一次执行完成后,紧接着就执行下一次任务

  • fixedDelay是配置的上一次任务执行结束到下一次执行开始的间隔时间,也就是说会等待上一次任务执行结束后,延迟间隔时间,再执行下一次任务

  • 代码示例:

  • @Slf4j
    @Component
    public class RunIntervalTestScheduler {
    ​
        @Scheduled(initialDelay = 1000, fixedDelay = 1000)
        public void fixedDelay() throws Exception {
            log.info("fixedDelay run");
            TimeUnit.SECONDS.sleep(3);
        }
    ​
    }
     
  • 执行结果:

  •  

  • 任务配置的fixedDelay为1s,执行日志打印的时间间隔都是4s左右,也就是上一次执行完成后,延迟1s后执行下一次任务

  • cron表达式如果配置为类似每秒执行、每分钟执行(例:0/1 * * * * ?, 每秒执行),调度跟fixedDelay是一致的,也是在上一次任务执行结束后,等待间隔时间

  • 代码示例:

  • @Slf4j
    @Component
    public class RunIntervalTestScheduler {
    ​
        @Scheduled(cron = "0/1 * * * * ?")
        public void cronRun() throws Exception{
            log.info("cron run");
            TimeUnit.SECONDS.sleep(3);
        }
    ​
    }

  • 执行结果:

  • 执行日志打印的时间间隔都是4s左右,也就是上一次执行完成后,延迟1s后执行下一次任务

  • cron表达式如果配置为固定时间执行(例:1 * * * * ?, 秒数为1时执行),若上一次任务没有执行完,则不会调度本次任务,跳过本次执行,等待下一次执行周期

  • 代码示例:

  • @Slf4j
    @Component
    public class RunIntervalTestScheduler {
    ​
        @Scheduled(cron = "1 * * * * ?")
        public void cronRun() throws Exception{
            log.info("cron run");
            TimeUnit.SECONDS.sleep(70);
        }
    ​
    }
    ​

  • 执行结果:

  • 上一次任务未执行完毕,则跳过了本次执行

写文章

热门文章

  • Consider defining a bean of type问题解决 51248
  • ES学习看这一篇文章就够了 16076
  • vue组件中重新渲染的3种方式 13709
  • 完美解决Column ‘xxx‘ in field list is ambiguous问题 12333
  • 一台电脑如何配置两个Jdk,然后任意切换使用? 9136

分类专栏

  • JAVA基础工作中实际总结 410篇
  • 编程学习 491篇
  • java错误分析 23篇
  • 软件 13篇
  • Java全套学习计划 18篇
  • 产品 10篇
  • 工具类软件 13篇
  • JAVA面试知识体系实战总结 28篇
  • 信息安全 4篇
  • 工作总结 87篇
  • SVN相关 6篇
  • Maven在eclipse中配置 1篇
  • 面试技巧 154篇
  • Java环境配置 3篇
  • Linux 3篇
  • 互联网创业 4篇
  • 数据库相关 6篇
  • idea项目中总结 4篇
  • oracle以及sql 11篇

最新评论

  • java后台怎么返回blob格式的文件流

    小王毕业啦: 博主的这篇文章让我对java后台返回blob格式的文件流有了更深入的了解,文中详细描述了后端返回blob文件流的步骤和方法,让我受益匪浅。博主的文章写作水平很高,逻辑清晰,让我在阅读过程中很容易理解和吸收。希望博主能够继续分享更多有价值的文章,让我们能够持续学习和成长。感谢博主的辛苦分享和支持!

  • java epoll网络编程

    CSDN-Ada助手: 推荐 Java 技能树:https://edu.csdn.net/skill/java?utm_source=AI_act_java

  • JavaWeb入门看这一篇文章就够了

    monbry: 言简意赅,深度好文

  • Consider defining a bean of type问题解决

    坐看昀起: 谢谢老哥

  • 深入解析java.lang.IllegalStateException异常

    小王毕业啦: 博主的文章真是太赞了!通过深入解析java.lang.IllegalStateException异常,我对这个主题有了全新的认识。文章中的细节描写非常到位,让我感受到了博主的深厚功底和专业知识。期待博主未来能够持续分享更多这样有价值的好文,同时也希望能够得到博主的指导,共同进步。非常感谢博主的分享和支持,让我在学习道路上受益匪浅!

大家在看

  • 【教学类-52-11】20240919动物数独(4宫格)1图、2图、6图、有答案、无答案 组合版18套
  • 【愚公系列】《AIGC辅助软件开发》018-AI辅助后端编程:快速生成接口文档 1033
  • java毕业设计-基于springboot+vue的社区汽车共享管理系统设计和实现,基于springboot的汽车共享租赁和交易平台设计和实现(源码+LW+部署文档+远程调试+代码讲解等)
  • uniapp(全端兼容) - 实现地图位置标点或搜索位置名称“一键导航“功能,唤起手机导航App软件(高德地图、百度地图、腾讯地图)导航页面,如果用户手机没有安装地图app,则跳转到各家地图网页版导航
  • AUTOSAR UDS 基础

最新文章

  • 架构师的七大核心能力
  • 开发易忽视的问题:InnoDB 行锁设计与实现
  • 架构之道:如何有效控制对象访问权
2024
09月 19篇
08月 16篇
07月 29篇
06月 16篇
05月 9篇
04月 22篇
03月 25篇
02月 17篇
01月 24篇
2023年299篇
2022年233篇
2021年86篇
2020年11篇
2019年27篇
2018年16篇

目录

目录

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

IT枫斗者

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或 充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值

玻璃钢生产厂家南京城市玻璃钢雕塑加工保定佛像玻璃钢雕塑校园玻璃钢人物雕塑小品商场美陈授权费郏县玻璃钢雕塑设计玻璃钢雕塑的主要有哪些性能黔东南商场美陈布置鄂尔多斯玻璃钢雕塑定做甘肃玻璃钢海豚雕塑定制贵阳景观玻璃钢雕塑制作优惠的玻璃钢迎宾雕塑敦化玻璃钢雕塑黑龙江抽象玻璃钢雕塑生产厂家玻璃钢景观雕塑有哪些制作玻璃钢北极熊雕塑摆件武汉玻璃钢人物不锈钢雕塑公司在邯郸哪里有玻璃钢花盆卖牛玻璃钢雕塑桂林玻璃钢雕塑厂家玻璃钢雕塑娃娃厂家供应沧州卡通玻璃钢雕塑价格公仔玻璃钢卡通雕塑销售方法玻璃钢疯狂外星人雕塑浙江广场玻璃钢雕塑厂家玻璃钢雕塑加工厂特别推荐玻璃钢雕塑凳子多少钱商场美陈的设计特点荥阳城市玻璃钢雕塑舟山玻璃钢人物雕塑定制长春玻璃钢圆门头雕塑香港通过《维护国家安全条例》两大学生合买彩票中奖一人不认账让美丽中国“从细节出发”19岁小伙救下5人后溺亡 多方发声单亲妈妈陷入热恋 14岁儿子报警汪小菲曝离婚始末遭遇山火的松茸之乡雅江山火三名扑火人员牺牲系谣言何赛飞追着代拍打萧美琴窜访捷克 外交部回应卫健委通报少年有偿捐血浆16次猝死手机成瘾是影响睡眠质量重要因素高校汽车撞人致3死16伤 司机系学生315晚会后胖东来又人满为患了小米汽车超级工厂正式揭幕中国拥有亿元资产的家庭达13.3万户周杰伦一审败诉网易男孩8年未见母亲被告知被遗忘许家印被限制高消费饲养员用铁锨驱打大熊猫被辞退男子被猫抓伤后确诊“猫抓病”特朗普无法缴纳4.54亿美元罚金倪萍分享减重40斤方法联合利华开始重组张家界的山上“长”满了韩国人?张立群任西安交通大学校长杨倩无缘巴黎奥运“重生之我在北大当嫡校长”黑马情侣提车了专访95后高颜值猪保姆考生莫言也上北大硕士复试名单了网友洛杉矶偶遇贾玲专家建议不必谈骨泥色变沉迷短剧的人就像掉进了杀猪盘奥巴马现身唐宁街 黑色着装引猜测七年后宇文玥被薅头发捞上岸事业单位女子向同事水杯投不明物质凯特王妃现身!外出购物视频曝光河南驻马店通报西平中学跳楼事件王树国卸任西安交大校长 师生送别恒大被罚41.75亿到底怎么缴男子被流浪猫绊倒 投喂者赔24万房客欠租失踪 房东直发愁西双版纳热带植物园回应蜉蝣大爆发钱人豪晒法院裁定实锤抄袭外国人感慨凌晨的中国很安全胖东来员工每周单休无小长假白宫:哈马斯三号人物被杀测试车高速逃费 小米:已补缴老人退休金被冒领16年 金额超20万

玻璃钢生产厂家 XML地图 TXT地图 虚拟主机 SEO 网站制作 网站优化