SpringBoot多线程环境下,解决多个定时器冲突问题

验证Springboot定时器默认的是单线程的

1.实现源代码
@Component
@EnableScheduling
@Slf4j
public class SchedulerTaskController {

    private static final SimpleDateFormat dateFormat=new SimpleDateFormat("HH:mm:ss");
    private int count=0;


    @Scheduled(cron="*/6 * * * * ?")
    @Async("threadPoolTaskExecutor")
    public void process(){
        log.info("英文:this is scheduler task runing "+(count++));
    }

    @Scheduled(fixedRate = 6000)
    @Async("threadPoolTaskExecutor")
    public void currentTime(){
        log.info("中文:现在时间"+dateFormat.format(new Date()));
    }
}

2.执行结果

在这里插入图片描述

3.结论

       可以看到所有定时任务都是通过线程scheduling-1来执行的,也就是单线程执行的。也就是说如果不同的定时任务在同一时间执行,就会出现争抢被线程执行权的情况,这样如果某一个线程抢到了被执行权,但是该线程执行时间又特别耗时,这就出现了其它线程一直处于等待状态情况,时间越久,累计等待的定时器越多,严重时引发雪崩。

验证Springboot定时器多线程执行

1.添加配置类
@Configuration
@EnableAsync
public class TaskScheduleConfig {
    /**使用多线程的时候,往往需要创建Thread类,或者实现Runnable接口,如果要使用到线程池,我们还需要来创建Executors,
     * 在使用spring中,已经给我们做了很好的支持。只要要@EnableAsync就可以使用多线程
     * 通过spring给我们提供的ThreadPoolTaskExecutor就可以使用线程池。
     */

    private static final int corePoolSize = 10;    // 默认线程数
    private static final int maxPoolSize = 100;    // 最大线程数
    private static final int keepAliveTime = 10;   // 允许线程空闲时间(单位:默认为秒),十秒后就把线程关闭
    private static final int queueCapacity = 200;  // 缓冲队列数
    private static final String threadNamePrefix = "it-is-threading-"; // 线程池名前缀

    @Bean("threadPoolTaskExecutor") // bean的名称,默认为首字母小写的方法名
    public ThreadPoolTaskExecutor getThread(){
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(corePoolSize);
        executor.setMaxPoolSize(maxPoolSize);
        executor.setQueueCapacity(keepAliveTime);
        executor.setKeepAliveSeconds(queueCapacity);
        executor.setThreadNamePrefix(threadNamePrefix);

        /**
         * 线程池拒绝任务的处理策略:
         *    当要创建的线程数量大于线程池的最大线程数的时候,新的任务就会被拒绝,就会调用这个接口里的这个方法;
         *    也可以自己实现这个接口,实现对这些超出数量的任务的处理。
         *    ThreadPoolExecutor提供的四种拒绝策略,分别是CallerRunsPolicy,
         *                                              AbortPolicy,
         *                                              DiscardPolicy,
         *                                              DiscardOldestPolicy
         *
         *      AbortPolicy:默认拒绝策略,直接抛出异常,也不执行超出的这个任务
         *      CallerRunsPolicy:在添加任务被拒绝添加后,会调用当前线程池所在的线程去执行被拒绝的任务,哪个线程
         *                        创建的线程池就由哪个线程run,如main线程创建线程池,由main去直接run(策略的缺点就是可能会阻塞主线程)
         *      DiscardPolicy:线程池拒绝的任务直接抛弃,不会抛异常也不会执行
         *      DiscardOldestPolicy:当任务被拒绝添加时,会抛弃任务队列中最旧的任务也就是最先加入队列的,再把这个新任务添加进去。
         */
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        //初始化
        executor.initialize();

        return executor;
    }
}

2.定时任务类
@Component
@EnableScheduling
@Slf4j
public class AsyncSchedulerTaskController {

    private static final SimpleDateFormat dateFormat=new SimpleDateFormat("HH:mm:ss");
    private int count=0;

    @Scheduled(cron="*/6 * * * * ?")
    @Async("threadPoolTaskExecutor")
    public void process(){
        log.info("英文:this is scheduler task runing "+(count++));
    }

    @Scheduled(fixedRate = 6000)
    @Async("threadPoolTaskExecutor")
    public void currentTime(){
        log.info("中文:现在时间"+dateFormat.format(new Date()));
    }
}
3.执行结果

在这里插入图片描述

4.结论:

       可以看到所有的定时任务启动后是由不同的线程执行的,也就解决了用多线程解决Springboot多定时器冲突的问题

Spring @Scheduled定时任务的三种定时类型

1. @Scheduled(cron=“0/5 * * * *?”)

       当时间达到设置的时间会触发事件。上面那个例子会每5秒执行一次,也有单线程和多线程的区分。

2. @Scheduled(fixedRate=2000)

       项目启动后立即执行一次,之后按照固定的时间间隔(如上设置的2秒)进行执行,但是如果在项目中使用的单线程执行定时任务,那么并不会按照时间间隔启动定时任务,而是等上一个执行结束,才会立即开启下一个,如:

设置的时间间隔为5秒,执行该任务的时间为10秒
假如第一次线程执行时间为12时10分05秒,则执行结束时间为12时10分15秒,但是在12时10分10秒已经过了5秒,按理应该执行第二次,但是并不会,这是因为在单线程环境中,总是需要等到上一个执行完毕下一个才能执行,所以12时10分10秒的会先被记录,等到第一次执行完毕即12时10分15秒,会进行被执行权争抢,如果顺利拿到被执行权,则会执行12时10分10秒被记录的定时任务,所以如果时间间隔设置太短而执行时间又不确定长,越到后期几乎是一个接一个执行,间隔时间相当于摆设。

但是如果开启多线程模式,如上第一次执行12时10分05秒,执行结束时间应该是12时10分15秒,但是在12时10分10秒时第二次执行时间到了,会开启第二个线程,也就是说会在12时10分10秒到12时10分15秒期间存在两个线程执行同一逻辑,(也就是说用 @Async 注解,使得任务不在 TaskScheduler 线程池中执行,而是每次创建新线程来执行)以此类推。

3. @Scheduled(fixedDelay=2000)

        会在上一次定时器结束后按照时间间隔开始执行下一次。

总结

fixedRate 每次任务结束后会从任务编排表中找下一次该执行的任务,判断是否到时机执行。fixedRate 的任务某次执行时间再长也不会造成两次任务实例同时执行,除非用了 @Async 注解。 fixedDelay 总是前一次任务完成后,延时固定长度然后执行一次任务.

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值