【SpringBoot】@Async注解的简单使用

250 阅读4分钟

本文主要记录了@Async注解的使用,@Async主要用于开启异步任务。

项目环境:JDK1.8、SpringBoot、MySQL。前置条件:需要创建一个SpringBoot项目。

行文逻辑按照如下顺序:

  • 默认线程池使用@Async
  • 自定义线程池
  • 异步失效
  • 带返回值的异步处理

默认线程池

在新建的SpringBoot项目中添加@EnableAsync,此时启动类代码如下:

@SpringBootApplication
@EnableAsync
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

controller层代码如下:

public class AsyncController {
​
    @Resource
    private IAsyncService asyncService;
​
    @GetMapping("default_thread.do")
    public void threadTest(){
​
        asyncService.syncMethod();
        asyncService.asyncMethod();
    }
​
}

service层代码如下:接口定义代码省略。

@Service
@Slf4j
public class AsyncServiceImpl implements IAsyncService {
    @Override
    public void syncMethod() {
        log.info("syncMethod threadName = {}", Thread.currentThread().getName());
    }
​
    @Override
    @Async
    public void asyncMethod() {
        log.info("asyncMethod threadName = {}", Thread.currentThread().getName());
    }
}
​

此时运行结果如下:

在没有配置线程池的情况下,springboot 会启动默认的线程池来开启任务。在上图的运行结果中也可以看到异步方法以异步运行了。使用了注解的线程名和controller中打印的线程名称不一样。

自定义线程池

由于默认线程池的弊端,在实际开发中,一般不会采用默认的线程池配置,因此需要我们自定义线程池配置,新增配置类,代码如下:

@Bean(name = "myTaskAsyncPool")
public Executor myTaskAsyncPool() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(10);
    executor.setMaxPoolSize(16);
    executor.setQueueCapacity(100);
    executor.setKeepAliveSeconds(10);
    executor.setThreadNamePrefix("my-thread-pool-");
    executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
    executor.initialize();
    return executor;
}

此时在service层添加如下方法:

@Override
@Async
public void asyncMethod() {
    log.info("asyncMethod threadName = {}", Thread.currentThread().getName());
}
// 新增的代码
@Override
@Async("myTaskAsyncPool")
public void asyncMethodCustomThreadPool() {
    log.info("asyncMethodCustomThreadPool threadName = {}", Thread.currentThread().getName());
}

运行结果如下:

从运行结果上可以看到自定义配置的线程池起作用了。

异步失效

@Async失效的场景:在当前类方法中自己调用自己的方法。

这是因为 @Async 注解的实现是基于 SpringAOP,而 AOP 的实现是基于动态代理模式实现的。那么注解失效的原因就很明显了,有可能因为调用方法的是对象本身而不是代理对象,因为没有经过 Spring 容器。除此之外方法的定义必须是 public

@Resource
@Lazy
private IAsyncService asyncService;
​
@Override
@Async
public void asyncInvalid() {
    log.info("asyncInvalid threadName = {}", Thread.currentThread().getName());
    this.asyncMethod();
}
@Override
@Async
public void asyncValid2() {
    log.info("asyncValid2 threadName = {}", Thread.currentThread().getName());
    // 通过自动注入来实现
    asyncService.asyncMethodCustomThreadPool();
}

从下图的运行结果中可以看到:asyncInvalid()异步失效了。通过自动注入的方法则没有。

异步任务的返回值

在前面的几个例子中示例的都是不需要返回值的情况,但是在实际的项目中、往往需要异步任务的返回值、根据这一步的返回结果做下一步处理,用@Async修饰的方法需要借助Future来实现,代码如下:

@Override
public Future<String> asyncReturnFuture() {
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
    // 模拟异步返回结果 可走db查询 redis缓存查询等
    AsyncResult<String> asyncResult = new AsyncResult<String>("return in async");
    return asyncResult;
}
​
@GetMapping("future_thread.do")
public String futureThreadTest() throws ExecutionException, InterruptedException {
    Future<String> stringFuture = asyncService.asyncReturnFuture();
    return stringFuture.get();
}

异步任务出现异常的情况

有时候异步任务的执行也不总是顺利的、总会出现异常的情况。针对无返回值的情况,可以修改配置类的代码:

@Configuration
public class ThreadPoolConfiguration implements AsyncConfigurer {
​
    @Bean(name = "myTaskAsyncPool")
    public Executor myTaskAsyncPool() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10);
        executor.setMaxPoolSize(16);
        executor.setQueueCapacity(100);
        executor.setKeepAliveSeconds(10);
        executor.setThreadNamePrefix("my-thread-pool-");
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }
​
    @Override
    public Executor getAsyncExecutor() {
        return AsyncConfigurer.super.getAsyncExecutor();
    }
​
   // 针对无返回值的
    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return new MyAsyncExceptionHandler();
    }
}
@Slf4j
class MyAsyncExceptionHandler implements AsyncUncaughtExceptionHandler {
    @Override
    public void handleUncaughtException(Throwable ex, Method method, Object... params) {
        log.info("Exception message - " + ex.getMessage());
        log.info("Method name - " + method.getName());
        for (Object param : params) {
            log.info("Parameter value - " + param);
        }
    }
}

修改完配置类之后,在service层代码如下:

@Override
@Async
public void asyncException() {
    throw new RuntimeException("asyncException");
}
@Override
@Async
public Future<String> asyncReturnFutureException() {
    try {
        TimeUnit.SECONDS.sleep(1);
        throw new RuntimeException("asyncReturnFutureException");
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
}
​

针对需要返回值的异步任务,我们可以在调用方通过try...catch来进行处理。在controller层添加如下代码:

@GetMapping("future_void_exception.do")
public void futureThreadExceptionTest1(){
    try {
        asyncService.asyncException();
    }catch (Exception e){
        log.error("futureThreadExceptionTest error",e);
    }
}
​
@GetMapping("future_return_exception.do")
public String futureThreadExceptionTest2(){
​
    try {
        Future<String> stringFuture = asyncService.asyncReturnFutureException();
        return stringFuture.get();
    } catch (InterruptedException e) {
        log.error("futureThreadExceptionTest error",e);
        return  "出错了";
    } catch (ExecutionException e) {
        log.error("futureThreadExceptionTest error",e);
        return  "出错了";
    }catch (Exception e){
        log.error("futureThreadExceptionTest error",e);
        return  "出错了" + e.getMessage();
    }
}

分别访问上述路由,可以看到异常处理的起作用了。

image-20240515111723413

参考资料: