@Schedule注解,多个定时任务执行时造成阻塞不能按时执行

1、问题描述

@Scheduled注解定时执行任务的时候是在一个单线程中,如果有多个任务,其中一个任务执行时间过长,则有可能会导致其他后续任务被阻塞直到该任务执行完成。也就是会造成一些任务无法定时执行的错觉。

源码:@Scheduled注解在注册时给定时任务生成了一个单线程的线程池(newSingleThreadScheduledExecutor())

 protected void scheduleTasks() {
        if (this.taskScheduler == null) {
            this.localExecutor = Executors.newSingleThreadScheduledExecutor();
            this.taskScheduler = new ConcurrentTaskScheduler(this.localExecutor);
        }

2、解决方式

2.1、扩大原定时任务线程池中的核心线程数

@Configuration
public class scheduleConfig implements SchedulingConfigurer {
 
    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        taskRegistrar.setScheduler(Executors.newScheduledThreadPool(50));
    }
}

2.2、使用@Async注解实现异步任务

这种方式比较简单,在定时任务上加上@Async注解,注意:需启动类配合加上 @EnableAsync才会生效

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

2.3、把Scheduled配置成成多线程执行

新增自定义线程池,配置线程池,启动类添加注解@EnableAsync

定时任务自动匹配线程池:@Async(配置文件中Bean的别名或线程池配置方法名)

@Component
public class asyncScheduledTaskConfig {
 
    @Bean("myAsync")
    public Executor myAsync() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 最大线程数
        executor.setMaxPoolSize(100);
        // 核心线程数
        executor.setCorePoolSize(100);
        // 任务队列的大小
        executor.setQueueCapacity(100);
        // 线程前缀名
        executor.setThreadNamePrefix("New-Thread-");
        // 线程存活时间
        executor.setKeepAliveSeconds(60);
        // 拒绝处理策略:直接抛出异常
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy() {
        });
        // 线程初始化
        executor.initialize();
        return executor;
    }
}

定时任务类中自动匹配Bean:

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

这种方法,每次定时任务启动的时候,都会创建一个单独的线程来处理。也就是说同一个定时任务也会启动多个线程处理。

例如:任务1和任务2一起处理,但是线程1卡死了,任务2是可以正常执行的。且下个周期,任务1还是会正常执行,不会因为上一次卡死了,影响任务1。

但是任务1中的卡死线程越来越多,会导致50个线程池占满,还是会影响到定时任务。

影响时的解决方法--重启。。。

/**

 * 拒绝处理策略

序号拒绝策略类型说明
1ThreadPoolExecutor.AbortPolicy默认拒绝策略,拒绝任务并抛出任务
2ThreadPoolExecutor.CallerRunsPolicy使用调用线程直接运行任务
3ThreadPoolExecutor.DiscardPolicy直接拒绝任务,不抛出错误
4ThreadPoolExecutor.DiscardOldestPolicy触发拒绝策略,只要还有任务新增,一直会丢弃阻塞队列的最老的任务,并将新的任务加入

拒绝策略一:AbortPolicy

默认拒绝策略,拒绝任务并抛出任务

// 核心线程数
int corePoolSize = 1;
// 最大线程数
int maximumPoolSize = 2;
// 线程存活时间
long keepAliveTime = 10;
// 线程存活时间单位
TimeUnit unit = TimeUnit.SECONDS;
// 有界队列 遵循 FIFO 原则
BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(1);
// 线程工厂
ThreadFactory threadFactory = Executors.defaultThreadFactory();


// 拒绝策略 默认拒绝策略,拒绝任务并抛出异常:
RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy();
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(corePoolSize,
				maximumPoolSize,
				keepAliveTime,
				unit,
				workQueue,
				threadFactory,
				handler);

拒绝策略二:CallerRunsPolicy

调用线程运行多余的任务。

更换拒绝策略,将上面的 AbortPolicy 换成 CallerRunsPolicy

RejectedExecutionHandler handler = new ThreadPoolExecutor.CallerRunsPolicy();

拒绝策略三:DiscardPolicy

拒绝任务,不会抛出错误。
更换策略,将CallerRunsPolicy 换成DiscardPolicy:

RejectedExecutionHandler handler = new ThreadPoolExecutor.DiscardPolicy();

拒绝策略四:DiscardOldestPolicy

只要还有任务新增,一直会丢弃阻塞队列的最老的任务,并将新的任务加入到阻塞队列中
更换策略,将DiscardPolicy 换成DiscardOldestPolicy:

RejectedExecutionHandler handler3 = new ThreadPoolExecutor.DiscardOldestPolicy();

  • 23
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值