前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >谨慎使用SpringBoot中的@Scheduled注解

谨慎使用SpringBoot中的@Scheduled注解

作者头像
IT大咖说
发布2021-06-15 17:43:37
5.6K0
发布2021-06-15 17:43:37
举报
文章被收录于专栏:IT大咖说

在最近的项目中,碰到了@Scheduled注解失效的问题,分析原因后,使用@Scheduled注解做定时任务需求需要格外小心,避免踩入不必要的坑。

比如,有一个需求:一是每隔5s做一次业务处理,另一个则是每隔10s做相应的业务处理,在Springboot项目中,代码如下:

代码语言:javascript
复制
@EnableScheduling
@Component
public class ScheduleTask {

    @Scheduled(cron = "0/5 * * * * ?")
    public void taskA() {
        System.out.println("执行了ScheduleTask类中的taskA方法");
    }

    @Scheduled(cron = "0/10 * * * * ?")
    public void taskB() {
        System.out.println("执行了ScheduleTask类中的taskB方法");
    }
}

@Component:是将ScheduleTask类注入到Spring容器中。

@Scheduled:表示这个方法是个定时任务

@EnableScheduling:开启定时任务

cron表达式:是一个字符串,字符串以5或6个空格隔开,分开共6或7个域,每一个域代表一个含义,分别为 [秒] [分] [小时] [日] [月] [周] [年]

如果你对cron表达式不太了解,可以在 https://cron.qqe2.com/网站按照自己的需求生成相应的cron表达式。

产生的问题

1. 定时器的任务默认是按照顺序执行的

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

那么这是为什么呢?

首先说明一下@Scheduled注解加载的过程,以及它是如何执行的。

解析@Scheduled注解

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

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

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

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

然后,在ThreadPoolTaskShcedule类中,会对线程池进行初始化,线程池的核心线程数量为1,

阻塞队列为DelayedWorkQueue。

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

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

代码语言:javascript
复制
@Slf4j
@EnableScheduling
@Component
public class ScheduleTask {
    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();
        log.info("taskB方法执行次数:{}", ++num);
        threadLocalA.set(num);
    }
}

输出结果:

那么如何解决顺序执行呢?答案是配置定时任务线程池:

代码语言:javascript
复制
@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仍然正常执行,并且其还复用了其它线程,导致执行次数发生了变化。


2. 当系统时间发生改变时,@Scheduled注解失效

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

那么原因是什么呢?

在查询资料后得出:

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

那么这时候怎么解决呢?

1. 重启项目。这在生产环境中肯定是不允许啦,所以Pass

2. 无奈之举,改方案。怎么改呢?就是不适用@Scheduled注解,改成 ScheduledThreadPoolExecutor进行替代。

举例说明:下面是我项目中所写的部分定时任务

ScheduledThreadPoolExecutor 执行流程:

  1. 当调用scheduleAtFixedRate()方法或者scheduleWithFixedDelay()方法时,会向ScheduledThreadPoolExecutor的DelayQueue添加一个实现了RunnableScheduledFuture接口的ScheduleFutureTask。
  2. 线程池中的线程从DelayQueue中获取ScheduleFutureTask,然后执行任务。

方法说明:

代码语言:javascript
复制
public ScheduledFuture scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnit unit)

scheduleAtFixedRate方法的作用是预定在初始的延迟结束后,周期性地执行给定的任务,周期长度为period,其中initialDelay为初始延迟。

代码语言:javascript
复制
public ScheduledFuture scheduleWithFixedDelay(Runnable command,long initialDelay,long delay,TimeUnit unit);

scheduleWithFixedDelay方法的作用是预定在初始的延迟结束后周期性地执行给定任务,在一次调用完成和下一次调用开始之间有长度为delay的延迟,其中initialDelay为初始延迟。

来源:

https://www.toutiao.com/i6937161276858647077/

“IT大咖说”欢迎广大技术人员投稿,投稿邮箱:aliang@itdks.com

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-06-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 IT大咖说 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 产生的问题
  • 1. 定时器的任务默认是按照顺序执行的
  • 2. 当系统时间发生改变时,@Scheduled注解失效
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档