前言
周日在家午休,刚醒,突然接到同事电话,说线上有个kafka消费者服务崩了,一直在重启,你去看看。一顿排查后,发现进程在启动后的一分钟内必定会占用大量内存,而且进程内的线程瞬间1万+。最后定位到某个方法是用了@Async注解在使用时未指定自定义的线程池,而且kafka。那它是何方神圣
@Async介绍
在Spring中,使用@Async直接的方法,称之为异步方法;这些方法执行的时候,将会在独立的线程中被执行,调用者无需等待它的完成,可继续其他的操作。
Spring默认配置
在spring boot项目中我们只需要添加@EnableAsync来开启异步调用,@Async异步方法默认使用Spring创建ThreadPoolTaskExecutor。我们也可以自己定义线程池(推荐)。下面是spring源码中默认的参数
从源码我们可以看出线程池的部分配置:
- 默认核心线程数:1
- 最大线程数:2147483647
- 队列容量是:2147483647
- 空闲线程保留时间:60s
奇怪,没有看到队列使用的是什么以及线程池拒绝策略,于是继续看源码,发现在queueCapacity大于0的时候用的是LinkedBlockingQueue队列,否者用的SynchronousQueue
那线程池拒绝策略呢?查看源码这个地方使用了策略模式,默认是AbortPolicy。
spring默认给的这些参数在并发不高的场景下其实问题不大。但是倘若并发上来这个这个默认的线程池隐患就体现出来了。为什么呢?风险点体现在最大线程数以及默认队列LinkedBlockingQueue的大小上面。
正常情况下,我们生产环境的会设置初始堆-Xms,最大堆的-Xmx等jvm参数,当接口并发远远大于自定义线程池的任务处理速度,那线程池的队列中会堆积很多任务,直接表现是JVM内存突然飙升。这种情况下。如果你直接在使用Spring默认的线程池,那这个LinkedBlockingQueue队列的默认容量就是你生产环境OOM的引爆点。哈哈
如何正确使用@Async
1、使用@EnableAsync注解开启
@EnableAsync
@SpringBootApplication
public class ApiClientApplication {
public static void main(String[] args) {
SpringApplication.run(ApiClientApplication.class, args);
}
}
2.自定义线程池参数
//@EnableAsync 也可以放此处
@Configuration
public class ThreadPoolConfig {
@Bean("taskExecutor")
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(20);
executor.setQueueCapacity(200);
executor.setKeepAliveSeconds(60);
//线程池名的前缀
executor.setThreadNamePrefix("taskExecutor-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
return executor;
}
}
3.使用
@Async("taskExecutor")
public void methodA(){
}
使用
1.我们使用单元测试测试一下,首先定义一个任务类
@Slf4j
@Component
public class Task {
@Async("taskExecutor")
public void doTask(int i) throws InterruptedException {
log.info("开始做任务:{}",i);
Random random = new Random();
Thread.sleep(random.nextInt(10000));
log.info("任务结束:{}",i);
}
}
2.单元测试类
@SpringBootTest
@Slf4j
class OrderServiceApiClientApplicationTests {
@Autowired
private Task task;
@Test
public void test() throws Exception {
for (int i = 0; i < 50; i++) {
task.doTask(i);
}
}
3.测试结果
原创不易,点个关注呗
本文暂时没有评论,来添加一个吧(●'◡'●)