0
点赞
收藏
分享

微信扫一扫

@Schedule注解单线程,造成定时任务阻塞不能按时执行

雪域迷影 2022-01-17 阅读 201

问题背景:
新增定时任务不能按时执行

原定时任务的实现方式:
通过注解@Scheduled(cron="XXXX")+启动类@EnableScheduling的方式

--------------------此处有补充。。。-------

debug方式查看定时任务的执行步骤发现错误:(该异常被吞掉)

public void com.example.myTest.crontask.scheduleTask.fiveSecondsTask1() throws java.lang.InterruptedException

解释:执行线程被中止异常

当某个线程处于长时间的等待、休眠或其他暂停状态,而此时其他的线程通过Thread的interrupt方法终止该线程时抛出该异常。

Java处理InterruptedException_Xiaowo-CSDN博客_java.lang.interruptedexception

--------------------补充结束。。。----------

造成定时任务不能按时执行的原因:

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

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

Scheduled-->ScheduledAnnotationBeanPostProcessor-->ScheduledTaskRegistrar-->scheduleTasks方法

解决办法一:

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

修改定时任务默认使用线程池中的线程数为50

这个方法,在程序启动后,会逐步启动50个线程,放在线程池中。每个定时任务会占用1个线程。但是相同的定时任务,执行的时候,还是在同一个线程中。

例如,程序启动,每个定时任务占用一个线程。任务1开始执行,任务2也开始执行。如果任务1卡死了,那么下个周期,任务1还是处于卡死状态,任务2可以正常执行。也就是说,任务1某一次卡死了,不会影响其他线程,但是他自己本身这个定时任务会一直等待上一次任务执行完成。

解决方法二:(√)

把Scheduled配置成成多线程执行

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

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

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

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

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

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

 解决方法三:

定时任务方法内部异步执行

同一个类中的定时任务共用一个线程池。

ThreadPoolTaskExecutor线程池的使用:

Spring线程池ThreadPoolTaskExecutor的使用 - 上善若泪 - 博客园

1. 处理流程

当一个任务被提交到线程池时,首先查看线程池的核心线程是否都在执行任务,否就选择一条线程执行任务,是就执行第二步。

查看核心线程池是否已满,不满就创建一条线程执行任务,否则执行第三步。

查看任务队列是否已满,不满就将任务存储在任务队列中,否则执行第四步。

查看线程池是否已满,不满就创建一条线程执行任务,否则就按照策略处理无法执行的任务。

在ThreadPoolExecutor中表现为:

如果当前运行的线程数小于corePoolSize,那么就创建线程来执行任务(执行时需要获取全局锁)。

如果运行的线程大于或等于corePoolSize,那么就把task加入BlockQueue。

如果创建的线程数量大于BlockQueue的最大容量,那么创建新线程来执行该任务。

如果创建线程导致当前运行的线程数超过maximumPoolSize,就根据饱和策略来拒绝该任务。

2. 关闭线程池

调用shutdown或者shutdownNow,两者都不会接受新的任务,而且通过调用要停止线程的interrupt方法来中断线程,有可能线程永远不会被中断,不同之处在于shutdownNow会首先将线程池的状态设置为STOP,然后尝试停止所有线程(有可能导致部分任务没有执行完)然后返回未执行任务的列表。而shutdown则只是将线程池的状态设置为shutdown,然后中断所有没有执行任务的线程,并将剩余的任务执行完。

3.配置线程个数

如果是CPU密集型任务,那么线程池的线程个数应该尽量少一些,一般为CPU的个数+1条线程。

如果是IO密集型任务,那么线程池的线程可以放的很大,如2*CPU的个数。

对于混合型任务,如果可以拆分的话,通过拆分成CPU密集型和IO密集型两种来提高执行效率;如果不能拆分的的话就可以根据实际情况来调整线程池中线程的个数。

4.监控线程池状态

常用状态:

taskCount:线程需要执行的任务个数。

completedTaskCount:线程池在运行过程中已完成的任务数。

largestPoolSize:线程池曾经创建过的最大线程数量。

getPoolSize获取当前线程池的线程数量。

getActiveCount:获取活动的线程的数量

通过继承线程池,重写beforeExecute,afterExecute和terminated方法来在线程执行任务前,线程执行任务结束,和线程终结前获取线程的运行情况,根据具体情况调整线程池的线程数量。

举报

相关推荐

0 条评论