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个线程池占满,还是会影响到定时任务。
影响时的解决方法--重启。。。
/**
* 拒绝处理策略
序号 | 拒绝策略类型 | 说明 |
1 | ThreadPoolExecutor.AbortPolicy | 默认拒绝策略,拒绝任务并抛出任务 |
2 | ThreadPoolExecutor.CallerRunsPolicy | 使用调用线程直接运行任务 |
3 | ThreadPoolExecutor.DiscardPolicy | 直接拒绝任务,不抛出错误 |
4 | ThreadPoolExecutor.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();