Spring多定时任务@Scheduled执行阻塞问题

本文探讨了Spring Boot项目中定时任务延迟执行的问题,通过对比单线程与多线程执行方式,介绍了如何使用@Async注解及自定义线程池来优化定时任务执行效率。

一. 问题描述
  最近项目中发现一个问题,计划每日凌晨4:40执行一个定时任务,使用注解方式: @Scheduled(cron = “0 40 4 * * ?”),cron表达式明显没有问题,但是这个定时任务总是不按时执行,有时候得等到8点多,有时候9点多才执行。后来查了下,原来这种定时方式默认是单线程执行的,恰好我这里有多个定时任务,并且其中有个在4:40之前的定时任务比较耗时,导致4:40的任务只能等待之前的任务执行完成才能够触发,所以要自己手动把定时任务设置成多线程的方式才行。

二. 场景复现
  项目描述:使用Springboot进行开发
  设置两个定时任务,每5s执行一次,并打印出其执行情况
  代码如下:

@Component
@Log4j2
public class ScheduledTask {
    @Scheduled(cron = "0/5 * * * * ?")
    public void task1() throws InterruptedException {
        log.info("I am task11111111, current thread: {}", Thread.currentThread());
        while (true) {
            //模拟耗时任务,阻塞10s
            Thread.sleep(10000);
            break;
        }
    }

    @Scheduled(cron = "0/5 * * * * ?")
    public void task2() {
        log.info("I am task22222222, current thread: {}", Thread.currentThread());
    }
}

  执行结果如下:

2019-04-24 17:11:15.008  INFO 16868 --- [   scheduling-1] com.example.demo.task.ScheduledTask      : I am task22222222, current thread: Thread[scheduling-1,5,main]
2019-04-24 17:11:15.009  INFO 16868 --- [   scheduling-1] com.example.demo.task.ScheduledTask      : I am task11111111, current thread: Thread[scheduling-1,5,main]
2019-04-24 17:11:25.009  INFO 16868 --- [   scheduling-1] com.example.demo.task.ScheduledTask      : I am task22222222, current thread: Thread[scheduling-1,5,main]
2019-04-24 17:11:30.002  INFO 16868 --- [   scheduling-1] com.example.demo.task.ScheduledTask      : I am task22222222, current thread: Thread[scheduling-1,5,main]
2019-04-24 17:11:30.003  INFO 16868 --- [   scheduling-1] com.example.demo.task.ScheduledTask      : I am task11111111, current thread: Thread[scheduling-1,5,main]
2019-04-24 17:11:40.004  INFO 16868 --- [   scheduling-1] com.example.demo.task.ScheduledTask      : I am task22222222, current thread: Thread[scheduling-1,5,main]

  由结果可见,task1与task2由同一个线程Thread[scheduling-1,5,main]执行,也即该定时任务默认使用单线程,并且由于task1阻塞了10s,导致本应5s执行一次的定时任务10s才执行一次。

三. 解决方案
  网上有多种解决方案,以下列举两种
  方案一:使用@Async注解实现异步任务
    这种方式比较简单,在定时任务上加上@Async注解,注意:需启动类配合加上 @EnableAsync才会生效
  代码如下:

@Component
@Log4j2
public class ScheduledTask {
    @Async
    @Scheduled(cron = "0/5 * * * * ?")
    public void task1() throws InterruptedException {
        log.info("I am task11111111, current thread: {}", Thread.currentThread());
        while (true) {
            //模拟耗时任务,阻塞10s
            Thread.sleep(10000);
            break;
        }
    }

    @Async
    @Scheduled(cron = "0/5 * * * * ?")
    public void task2() {
        log.info("I am task22222222, current thread: {}", Thread.currentThread());
    }
}

  运行结果:

2019-04-24 17:03:00.024  INFO 2152 --- [         task-1] com.example.demo.task.ScheduledTask      : I am task22222222, current thread: Thread[task-1,5,main]
2019-04-24 17:03:00.024  INFO 2152 --- [         task-2] com.example.demo.task.ScheduledTask      : I am task11111111, current thread: Thread[task-2,5,main]
2019-04-24 17:03:05.001  INFO 2152 --- [         task-3] com.example.demo.task.ScheduledTask      : I am task11111111, current thread: Thread[task-3,5,main]
2019-04-24 17:03:05.001  INFO 2152 --- [         task-4] com.example.demo.task.ScheduledTask      : I am task22222222, current thread: Thread[task-4,5,main]
2019-04-24 17:03:10.002  INFO 2152 --- [         task-5] com.example.demo.task.ScheduledTask      : I am task22222222, current thread: Thread[task-5,5,main]
2019-04-24 17:03:10.003  INFO 2152 --- [         task-6] com.example.demo.task.ScheduledTask      : I am task11111111, current thread: Thread[task-6,5,main]

  由运行日志可见,定时每5s执行一次已生效,且每次任务使用的线程不一样,也即实现了多线程执行定时任务,不会出现任务等待现象。此方式据说默认线程池大小为100,要是任务不多的话有点大材小用了,所以我觉得第二种方式比较好。
  方案二:手动设置定时任务的线程池大小
  定时任务代码部分还原,不使用@Async注解,新增启动代码配置:

@Configuration
public class AppConfig implements SchedulingConfigurer {
    @Bean
    public Executor taskExecutor() {
    	//指定定时任务线程数量,可根据需求自行调节
        return Executors.newScheduledThreadPool(3);
    }

    @Override
    public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
        scheduledTaskRegistrar.setScheduler(taskExecutor());
    }
}

  运行结果如下:

2019-04-24 17:26:15.008  INFO 2164 --- [pool-1-thread-2] com.example.demo.task.ScheduledTask      : I am task22222222, current thread: Thread[pool-1-thread-2,5,main]
2019-04-24 17:26:15.008  INFO 2164 --- [pool-1-thread-1] com.example.demo.task.ScheduledTask      : I am task11111111, current thread: Thread[pool-1-thread-1,5,main]
2019-04-24 17:26:20.002  INFO 2164 --- [pool-1-thread-2] com.example.demo.task.ScheduledTask      : I am task22222222, current thread: Thread[pool-1-thread-2,5,main]
2019-04-24 17:26:25.001  INFO 2164 --- [pool-1-thread-2] com.example.demo.task.ScheduledTask      : I am task22222222, current thread: Thread[pool-1-thread-2,5,main]
2019-04-24 17:26:30.001  INFO 2164 --- [pool-1-thread-1] com.example.demo.task.ScheduledTask      : I am task11111111, current thread: Thread[pool-1-thread-1,5,main]
2019-04-24 17:26:30.001  INFO 2164 --- [pool-1-thread-3] com.example.demo.task.ScheduledTask      : I am task22222222, current thread: Thread[pool-1-thread-3,5,main]
2019-04-24 17:26:35.001  INFO 2164 --- [pool-1-thread-3] com.example.demo.task.ScheduledTask      : I am task22222222, current thread: Thread[pool-1-thread-3,5,main]

  由结果可见,第二种方式也实现了多线程任务调度。
四. 总结
  两种方式各有优缺点:

比较方案一方案二
优点注解方式使用简单,代码量少配置灵活,线程数可控
缺点线程数不可控,可能存在资源浪费需要增加编码

  留个坑,从日志上看@Async方式针对同一任务也是异步的,也即task1每5s会执行一次,但是方式二貌似对同一个任务不会生效,task1执行的时候需等待上一次执行结束才会触发,并没有每5s执行一次。关于这个现象,下次再琢磨…

参考链接:https://segmentfault.com/a/1190000015267976

<think>我们使用`@Scheduled`注解来配置定时任务,每天下午5点执行一次。在Spring中,我们可以使用cron表达式来配置执行时间。每天下午5点对应的cron表达式为:`0017**?`下面是一个简单的配置示例:1.首先,确保你的Spring项目已经启用了定时任务。可以在配置类上添加`@EnableScheduling`注解。2.在需要定时执行的方法上添加`@Scheduled(cron="0017**?")`注解。示例代码:```javaimportorg.springframework.scheduling.annotation.EnableScheduling;importorg.springframework.scheduling.annotation.Scheduled;importorg.springframework.stereotype.Component;@Component@EnableSchedulingpublicclassScheduledTasks{//每天下午5点执行@Scheduled(cron="0017**?")publicvoidexecuteDailyAtFivePM(){//这里写定时任务的业务逻辑System.out.println("定时任务执行:每天下午5点");}}```注意:-上面代码中,`@EnableScheduling`注解可以放在配置类上,通常我们放在主配置类或者专门的任务配置类上,这里为了简单直接放在了组件类上(也是可以的)。-cron表达式的格式:秒分时日月周年(年可省略)。所以下午5点就是17时,对应的cron表达式为:0分0秒17时,即`0017**?`。另外,根据引用[1]中提供的动态修改cron表达式的例子,如果你需要动态修改定时任务时间,可以考虑使用`SchedulingConfigurer`接口。但是,如果你只需要固定的每天下午5点执行,那么直接使用`@Scheduled(cron="0017**?")`就足够了。引用[2]提到了`fixedRate`和`initialDelay`,但这里我们不需要,因为我们使用的是cron表达式。因此,我们给出一个简单的固定时间(每天下午5点)执行定时任务示例。如果以后需要动态修改执行时间,可以参考引用[1]中的方法,通过实现`SchedulingConfigurer`接口来动态设置cron表达式。注意:上面的cron表达式中的时区默认是服务器所在的时区。如果你的服务器时区和你所在的时区不一致,你可能需要设置时区。例如,如果你希望使用东八区(北京时间),可以在cron表达式中指定时区,但请注意,Spring@Scheduled注解的cron属性并不支持直接设置时区(在Spring5.0之前),但我们可以通过设置`zone`属性来指定时区。例如:```java@Scheduled(cron="0017**?",zone="Asia/Shanghai")```这样就会按照北京时间每天下午5点执行。总结:-固定每天下午5点执行:使用cron表达式`0017**?`-如果需要指定时区,可以加上`zone`属性,如`zone="Asia/Shanghai"`。因此,修改后的代码为:```java@Component@EnableSchedulingpublicclassScheduledTasks{@Scheduled(cron="0017**?",zone="Asia/Shanghai")publicvoidexecuteDailyAtFivePM(){//业务逻辑}}```这样,你的定时任务就会在北京时间每天下午5点执行了。</think> 在Spring框架中使用`@Scheduled`注解实现每天下午5点执行定时任务,具体配置如下: ### 核心配置代码 ```java import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; @Component @EnableScheduling public class DailyTaskScheduler { // 每天下午5点执行(24小时制) @Scheduled(cron = "0 0 17 * * ?") public void executeAt5PM() { // 任务逻辑 System.out.println("定时任务执行:北京时间每天17:00"); } } ``` ### 关键说明 1. **cron表达式**: - `0 0 17 * * ?` 表示每天17:00(下午5点) - 格式分解:秒(0) 分(0) 时(17) 日(*) 月(*) 周(?) - 时区默认采用服务器时区,需确保服务器时区正确 2. **必要注解**: - `@EnableScheduling`:需在配置类或主类上启用定时任务(若此处未加,需在其他配置类添加) - `@Component`:将类声明为Spring组件 3. **时区配置**(可选): ```java @Scheduled(cron = "0 0 17 * * ?", zone = "Asia/Shanghai") ``` 明确指定时区可避免服务器时区不一致问题 4. **执行机制**: - 任务会在每天17:00:00准时触发 - 若任务执行时间超过24小时,会导致后续执行阻塞(需优化长时间任务) ### 注意事项 1. 避免在`@Scheduled`方法内编写阻塞性操作 2. 节点部署时需考虑分布式任务协调(如使用Quartz或ShedLock) 3. Spring Boot需确保已添加依赖: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> ``` > 引用说明: > cron表达式格式规范参考Spring官方文档[^1],时区配置机制详见Spring Framework定时任务模块文档[^2] ---
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值