一、 OOM错误
程序报错: OutOfMemoryError:unable to create new native thread
原因:
springboot-2.1.0
以下版本(不含2.1.0)默认不适用线程池,异步方法每次被调用都会创建新的线程
具体源码分析见另一篇文章:【SpringBoot】 @Async OOM的坑及源码解析
解决: 配置线程池
1.1 @Configuration方式
Springboot-2.1.0 以下版本默认使用SimpleAsyncTaskExecutor
不是线程池,有OOM问题,必须通过手动自定义线程池方式解决
方案一
:自定义线程池 Bean,不自定义异常处理
package com.qbhj.casespringboot.async.config;
import java.util.concurrent.ThreadPoolExecutor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
@EnableAsync
@Configuration
public class AsyncConfig {
/**
* 自定义 异步线程池,若同类型Bean全局唯一可以不指定 bean名称,否则要指定为 taskExecutor
*/
@Bean("taskExecutor")
public ThreadPoolTaskExecutor threadPoolTaskExecutor2() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setMaxPoolSize(200);
executor.setCorePoolSize(8);
executor.setQueueCapacity(200);
executor.setKeepAliveSeconds(60);
executor.setThreadNamePrefix("task-executor-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
// 设置任务装饰器,执行Runnable之前调用,按需设置即可
// executor.setTaskDecorator(new CustomTaskDecorator());
executor.initialize();
return executor;
}
}
方案二
: 实现AsyncConfigurer
接口,重写getAsyncExecutor(),getAsyncUncaughtExceptionHandler() 方法
package com.qbhj.casespringboot.async.config;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.task.TaskExecutor;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
@Slf4j
@EnableAsync
@Configuration
public class AsyncConfig implements AsyncConfigurer {
/**
* 重写getAsyncExecutor, 自定义异步/task 线程池
*/
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setMaxPoolSize(200);
executor.setCorePoolSize(8);
executor.setQueueCapacity(200);
executor.setKeepAliveSeconds(60);
executor.setThreadNamePrefix("task-executor-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
// 设置任务装饰器,执行Runnable之前调用,按需设置即可
// executor.setTaskDecorator(new CustomTaskDecorator());
executor.initialize();
return executor;
}
/**
* 可选,按需重写AsyncUncaughtExceptionHandler方法,执行异常处理器
*/
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new CustomAsyncUncaughtExceptionHandler();
}
/**
* 自定义异步异常处理器
*/
static class CustomAsyncUncaughtExceptionHandler implements AsyncUncaughtExceptionHandler {
@Override
public void handleUncaughtException(Throwable ex, Method method, Object... params) {
//此demo只打印日志,实际项目以具体业务需求来处理
log.error(">>> CustomAsyncUncaughtExceptionHandler,class:{}, method: {}, params: {}, error: {}",
method.getDeclaringClass().getSimpleName(), method.getName(), Arrays.toString(params),
ex.getMessage());
}
}
}
1.2. yml方式(springboot-2.1.0及以上版本,含2.1.0)
Springboot-2.1.0及以上版本,可以使用默认自动装配的线程池,但是默认参数不合理,需要根据自身服务器配置和业务需求,通过yml来合理配置线程池参数
参照:官网Task Execution and Scheduling
Tips:
若需要自定义异常处理器,则不能使用默认线程池,参照 配置文件方式:方案二:实现AsyncConfigurer接口
的方式来自定义线程池
spring:
task:
execution:
pool:
# 默认 Integer.MAX_VALUE
max-size: 200
# 默认 Integer.MAX_VALUE
queue-capacity: 200
# 默认 60s
keep-alive: 10s
二、异步失效
@Async
注解添加在需要异步的类
或者方法
上即可开启异步,在类上,则该类所有方法都为异步方法
注意异步不生效场景,如下表:
不管是同一个类内中本地调用,还是跨类调用,同步方法调用异步方法,异步都不生效,都按同步执行
调用类型 | 发起调用方法 | 被调用方法 | 异步是否生效 | 备注 |
---|---|---|---|---|
同类本地调用 | 异步1 | 异步2 | 是/否 | 异步1生效,异步2使用异步1的线程执行,异步2没有异步 |
同类本地调用 | 异步1 | 同步2 | 是 | 异步1生效,被调用的同步1,使用异步1线程执行 |
同类本地调用 | 同步1 | 异步2 | 否 | 同步1 main方法执行,被调用异步2 使用main线程执行,异步失效 |
跨类调用 | 异步1 | 异步2 | 是/是 | 异步1 生效,异步2生效,异步1和异步2各自使用独立线程执行 |
跨类调用 | 异步1 | 同步2 | 是 | 异步1 生效,被调用的同步1 使用异步1线程执行 |
跨类调用 | 同步1 | 异步2 | 是 | 同步1 main线程执行,异步2 异步线程池执行 |
测试代码见Gitee,文章末提供地址
三、异步方法返回值获取不到
1、直接返回:获取不到返回值
@Test
public void returnString() throws InterruptedException {
System.out.println(">>> 1、returnString start...");
String text = returnService.returnString("text");
Thread.sleep(1000L);//阻塞主线程,等异步执行完
System.out.println(">>> returnString : " + text);
System.out.println(">>> 1、returnString end... ");
}
结果
2、直接返回 基本类型
,报错AopInvocationException
,AOP不支持基本类型的返回值
@Test
public void returnInt() {
System.out.println(">>> 2、returnInt start...");
int i = returnService.returnInt();
System.out.println(">>> 2、returnInt end... ");
}
结果
3、返回Future
包装的类型AsyncResult<String>
,通过future.get()
获取,可以正常获取到返回值
@Test
public void returnFutureString() throws ExecutionException, InterruptedException {
System.out.println(">>> 3、returnFutureString start...");
Future<String> future = returnService.returnFutureString("text");
System.out.println(">>> returnFutureString : " + future.get());
System.out.println(">>> 3、returnFutureString end... ");
}
结果
Tips: @Async
底层通过动态代理增强实现,原始逻辑构造为Callable,通过CompletableFuture来执行,AsyncResult
对CompletableFuture
返回结果进行了封装
四、异步异常处理
重写 org.springframework.scheduling.annotation.AsyncConfigurer#getAsyncUncaughtExceptionHandler
@Slf4j
@EnableAsync
@Configuration
public class AsyncConfig implements AsyncConfigurer {
// ...... 忽略其他 ......
/**
* 重写AsyncUncaughtExceptionHandler方法,执行异常处理器
*/
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new CustomAsyncUncaughtExceptionHandler();
}
/**
* 自定义异步异常处理器
*/
static class CustomAsyncUncaughtExceptionHandler implements AsyncUncaughtExceptionHandler {
@Override
public void handleUncaughtException(Throwable ex, Method method, Object... params) {
//只打印日志,实际项目以具体业务需求来处理
log.error("\r\n>>> CustomAsyncUncaughtExceptionHandler,class:{}, method: {}, params: {}, error: {}",
method.getDeclaringClass().getSimpleName(), method.getName(), Arrays.toString(params),
ex.getMessage());
}
}
}
测试方法
@Service
public class AsyncExceptionService {
private final static String CLAZZ = AsyncExceptionService.class.getSimpleName();
/**
* 验证 异常处理器
*/
@Async
public void clazzError(String a, String b) {
System.out.println(Log.log(CLAZZ, "clazzError"));
int i = 1 / 0;
}
}
单元测试
@SpringBootTest
public class AsyncExceptionTest {
@Autowired
private AsyncExceptionService asyncExceptionService;
/**
* CustomAsyncUncaughtExceptionHandler test,有效
*/
@Test
public void asyncExceptionHandlerTest() throws InterruptedException {
System.out.println(">>> asyncExceptionHandlerTest start....<<<\r\n");
asyncExceptionService.clazzError("hello", "exception");
Thread.sleep(1000L);
System.out.println("\r\n>>> asyncExceptionHandlerTest end <<<");
}
}
结果
五、代码
https://gitee.com/qbhj/java-cases/tree/master/case-springboot-async
如有错误欢迎指正,有其他场景未涵盖的可留言回复,持续补充完善此文!
谢谢!