问题场景:系统中@Scheduled设置的定时任务每两分钟执行一次,某一天上线了后发现@Scheduled不按时执行,经过排查发现是Spring内部某些内容共用了默认线程池,导致线程池资源不够而抢占资源导致。当时上线的是Kafka内容,引用了spring-kafka,经查发现其使用的线程池和@Scheduled是相同的默认线程池所以出现的问题,Kafka的大量并发持续占满线程池,导致定时任务抢占不到。
文章目录
下面是对@Async的源码进行分析为例,分析下线程池的创建过程。对源码不感兴趣的,可以跳到最后看解决这个问题的方法代码。
源码分析过程
@EnableAsync
开启spring异步执行器,需要联合@Configuration注解一起使用
@Async
该注解可以标记一个异步执行的方法
,也可以用来标注类
,表示类中的所有方法都是异步执行的。
@Async
*注意:@Async如果A类的A.a()方法调用同类的`@Async A.b()`方法异,会变成同步,因为底层实现是代理对注解扫描实现的,A.a()方法上没有注解,没有生成相应的代理类*返回值:void或AsyncResult或CompletableFuture
可以自己指定执行器的beanName: @Async(“xxxExecutor”)
@EnableAsync
直接看源码注释,解释的很清楚,重要的内容有下面这些(其它点在另一篇详细解析中介绍)
- 搭配@Configuration一起使用
- 默认Spring会寻找已经定义的线程池,已定义的
org.springframework.core.task.TaskExecutor
实例或名称为taskExecutor
的实例,如果都没有则会用org.springframework.core.task.SimpleAsyncTaskExecutor
。 默认异步的异常只会打印日志 - 想自定义线程池或者异常捕获可以通过实现
AsyncConfigurer
来实现,如下
@Configuration
@EnableAsync
public class AppConfig implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(7);
executor.setMaxPoolSize(42);
executor.setQueueCapacity(11);
executor.setThreadNamePrefix("MyExecutor-");
executor.initialize();
return executor;
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new MyAsyncUncaughtExceptionHandler();
}
}
@EnbaleAsync的属性annotation
定义需要被扫描的注解,默认的Spring的
@Async
和EJB的@javax.ejb.Asynchronous
注解会被扫描,用户可以通过这个属性设置自定义的注解来被扫描为异步类或方法
@Import(AsyncConfigurationSelector.class)
public @interface EnableAsync {
Class<? extends Annotation> annotation() default Annotation.class;
... ...
}
如果你了解SpringBoot的自动装配原理,就知道看这类源码一眼看到就是@Import(AsyncConfigurationSelector.class)
AsyncConfigurationSelector
@Override
@Nullable
public String[] selectImports(AdviceMode adviceMode)