SpringBoot中的定时任务@Scheduled的使用详情及可能出现的坑

实际项目中遇到的问题:定时任务偶尔不执行,即使执行了 也是执行部分就意外终止了;
通过下面这个方法实际也解决了问题:
参考地址

1.@Scheduled注解
在SpringBoot项目中使用定时任务时可以使用@Scheduled标注在需要定时执行的方法上。该注解位于spring-context.jar包中,关于@Scheduled的具体描述如下:在这里插入图片描述
在这里插入图片描述
注意:@Scheduled注解要生效还需要在系统启动类或配置类上添加@EnableScheduling注解

2.简单使用@Scheduled注解
2.1 首先这里创建了一个普通的SpringBoot项目叫SpringbootApplication,在启动类上添加@EnableSchedling注解

@SpringBootApplication
@EnableScheduling
public class SpringbootApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringbootApplication.class, args);
    }
}

2.2 创建定时任务调度类TestSchedule,并定义taskSchedule1方法使用@Scheduled(cron = “0/10 * * * * ?”)标注,表示该方法从0秒开始,每隔10秒执行一次,方法内部获取了执行当前任务的线程,打印了任务开始和结束的时间、线程ID、线程的名字,sleep 5秒表示任务执行完成需要花费5秒时间。使用@Component注解,交给Spring容器管理。

@Component
public class TestSchedule {
    @Scheduled(cron = "0/10 * * * * ?")
    public void taskSchdule1() throws InterruptedException {
        Thread t = Thread.currentThread();
        System.out.println("taskSchule1 "+ DateTimeUtils.dateToTimeStr(new Date()) +" ThreadID:"+ t.getId() +" "+t.getName());
        Thread.sleep(5000);
        System.out.println("taskSchule1 end "+ DateTimeUtils.dateToTimeStr(new Date()) +" ThreadID:"+ t.getId() +" "+t.getName());
    }
}

2.3 运行结果
到这里最简单的定时任务调度就算完成了,启动项目,打印结果如下:
在这里插入图片描述运行结果正常,每隔10秒执行一次,每次执行花费5秒时间。

3.第一个坑
我们现在在TestSchedule类中添加第二个需要任务调度方法,每隔3秒执行一次。

@Scheduled(cron = "0/3 * * * * ?")
    public void taskSchdule2(){
        Thread t = Thread.currentThread();
        System.out.println("taskSchule2 "+ DateTimeUtils.dateToTimeStr(new Date()) +" ThreadID:"+  t.getId() +" "+t.getName());
    }

再次运行,结果如下:
在这里插入图片描述
这里我们发现任务2,在a、b两处时间相差7秒已超过3秒,显然b处是在任务1结束之后立刻执行的,并且任务1和任务2都是同一个线程执行的。
因为: Spring中@EnableScheduling和@Scheduled标注的定时任务默认是单线程执行的,这里任务1执行任务需要花费较长时间,所有阻塞了任务2的执行。

  1. 使用@Async和@EnableAsync异步执行任务
    事实上在Spring的定时任务包中提供了@EnableAsync和@Async注解用于多线程异步执行任务。
    首先在启动类上添加@EnableAsync注解,并在TestSchedule类上标注@Async注解,表示该类中所有标注了@Scheduled的方法都使用异步处理方式。
    在这里插入图片描述
    在这里插入图片描述
    再次运行项目,结果如下:
    在这里插入图片描述
    此时,任务1和任务2均运行正常,并且任务1和任务2都是不同线程在执行,不会出现任务之间相互阻塞的情况。
    这里是解决了第一个坑的问题,但是实际上可能引入第二个坑。

  2. 第二个坑
    这里我们稍作修改将任务的睡眠时间改成11秒 Thread.sleep(11000);,此时任务1的执行时间已经超过了它的调度时间。再次运行程序结果如下:
    在这里插入图片描述
    观察发现任务2正常执行,但是任务1中a、b和c、d两组出现了交叉,两组是不同线程执行的,因为任务1的执行时间超过了调度时间,所以,a处开始执行,在未执行完成的情况下,任务的调度时间到了,其他线程有立马调度了任务从c处开始执行。
    这是使用@EnableAsync和@Async可能会出现的问题。

  3. 解决坑1和坑2
    再次修改代码,去掉5中的@EnableAsync和@Async注解,去掉2中的@EnableScheduling注解,
    在这里插入图片描述
    在这里插入图片描述
    创建一个任务配置类ScheduleConfig 实现SchedulingConfigurer接口的configureTasks方法,使用参数taskRegistrar为任务调度创建线程池

@Configuration
@EnableScheduling
public class ScheduleConfig implements SchedulingConfigurer {

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

    @Bean(destroyMethod="shutdown")
    public Executor taskExecutor() {
        return Executors.newScheduledThreadPool(10);
    }
}

运行程序结果如下:
在这里插入图片描述
现在任务1和任务2均运行正常,并且任务1不会出现坑2中的交叉现象,任务1第二次调度会等到第一次调度执行完毕后地下一个调度时间点才会执行。

总结
SpringBoot中可以使用@EnableScheduling和@Scheduled注解实现定时任务调度,但是注意默认所有任务都被单个线程调度的,有可能任务之间发生阻塞现象,可以使用@EnableAsync和@Async注解实现异步多线程任务调度,但需要注意任务执行时间如果大于任务调度周期时间,可能出现同一个任务交叉执行的情况。当然也可以使用第6步中的方法避免上述问题。?

总结:
①@Scheduled 单线程处理,多任务时 ,一个任务耗时比较久,会阻塞等待;即使上个任务异常处理
②3 为定时任务,实际已经开始执行,但情况是 :实际数据有上千条,但是仅仅更新了一条;
怀疑为其他任务报错导致任务意外终止, 但是实测发现一个任务异常并不会影响线程执行下一个任务;
在这里插入图片描述

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值