【SpringBoot】使用@Async常见问题

一、 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... ");
}

结果
返回Future

Tips: @Async底层通过动态代理增强实现,原始逻辑构造为Callable,通过CompletableFuture来执行,AsyncResultCompletableFuture返回结果进行了封装

四、异步异常处理

重写 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


如有错误欢迎指正,有其他场景未涵盖的可留言回复,持续补充完善此文!
谢谢!

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值