Spring Async不得不知的用法

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/believer123/article/details/80849728

在Spring中使用需要对方法进行异步操作的时候,只需要在对应的方法上加上 @Async 注解就好了,如果想要获取返回值或者进行监听呢?
首先,整合异步框架需要在SpringBoot入口类中添加@EnableAsync注解,表明开启异步框架。
从@Async类的注释上得知

In terms of target method signatures, any parameter types are supported. 
However, the return type is constrained to either void or java.util.concurrent.Future. 
In the latter case, you may declare the more specific org.springframework.util.concurrent.ListenableFuture or java.util.concurrent.CompletableFuture types which allow for richer interaction with the asynchronous task and for immediate composition with further processing steps.

那么就有以下几种使用方式

  • 不需要获取返回内容,返回void即可
    @Async
    public void testAsync(int index) {
    }
  • 需要获取结果时可以使用Future返回
    @Async
    public Future<String> testFuture(int index) {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return AsyncResult.forValue("FutureTask:" + index);
    }
  • Future拓展:ListenableFuture

    @Async
    public ListenableFuture<String> testLisenableAsync(int index) {
        if (index == 10)
            throw new RuntimeException("testLisenableAsync:" + index);
        return AsyncResult.forValue("testValue:" + index + ",threadId:" + Thread.currentThread().getId());
    }

例如: 在操作完成和操作失败后进行log

  asyncService.testLisenableAsync(index).addCallback(s -> {
                    logger.info("asyncSuccess----->" + s);
                }, e -> {
                    logger.error("asyncFail", e);
                });
  • Future拓展:CompletableFuture
    @Async
    public CompletableFuture<String> testCompletableFuture(int index) {
        return CompletableFuture.completedFuture("testCompletableFuture:" + index);
    }

例如,在执行完毕后进行回调操作,注意回调是异步操作的

 CompletableFuture<String> future = asyncService.testCompletableFuture(1);
            future.thenAccept(v -> {
                logger.info("---->CompletableFuture get:" + v);
            });

线程池

既然使用到了@Async的异步操作功能,那么必然有对应的线程操作,从注释上可以得知,如果系统中没有TaskExecutor实例或者没有bean的名称叫taskExecutor的Executor对象,默认会使用SimpleAsyncTaskExecutor线程池。
官方说明如下:

By default, Spring will be searching for an associated thread pool definition: either a unique org.springframework.core.task.TaskExecutor bean in the context, 
or an java.util.concurrent.Executor bean named "taskExecutor" otherwise. If neither of the two is resolvable, a org.springframework.core.task.SimpleAsyncTaskExecutor will be used to process async method invocations. 
Besides, annotated methods having a void return type cannot transmit any exception back to the caller. By default, such uncaught exceptions are only logged.

一看到这里的注释就以为Spring已经有默认的线程池了,所以不需要我们自己去自定义线程池了,交给Spring就万事大吉了。直到有一天有空了继续跟进,进入SimpleAsyncTaskExecutor 类中查看注释如下:

TaskExecutor implementation that fires up a new Thread for each task, executing it asynchronously.
Supports limiting concurrent threads through the "concurrencyLimit" bean property. By default, the number of concurrent threads is unlimited.
NOTE: This implementation does not reuse threads! Consider a thread-pooling TaskExecutor implementation instead, in particular for executing a large number of short-lived tasks.

什么鬼,这里的这个线程池竟然没有进行线程的复用,而是每次操作都新开一个线程进行处理。通常一说线程池联想到的就是线程的复用,要不然要线程池干嘛。所以这里适合少量异步任务的时候,如果有大量的异步任务,这里就不推荐使用默认的线程池了。

自定义线程池

在@Async的注释中也说到了,会自动去寻找TaskExecutor实例或者bean的名称叫taskExecutor的Executor对象,那么实现就非常简单了。

@Configuration
public class AsyncThreadPoolConfiguration {

    @Bean
    public ThreadPoolTaskExecutor taskExecutor() {
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        taskExecutor.setCorePoolSize(100);
        taskExecutor.setMaxPoolSize(500);
        taskExecutor.setThreadNamePrefix("async-thread-pool-");
        return taskExecutor;
    }
}

在@Async中还提及:By default, such uncaught exceptions are only logged.如果线程执行的过程中抛出了异常,默认是只是log出来而已。如果想要在线程异常后进行自己的操作呢?

  • 使用ListenableFuture作为返回值,在每个方法中添加回调操作
  • 自定义线程异常处理类,实现AsyncConfigurer接口
@Component
public class AsyncThreadPoolConfiguration2 implements AsyncConfigurer {
    private Logger logger = LoggerFactory.getLogger(getClass());

    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        taskExecutor.setCorePoolSize(100);
        taskExecutor.setMaxPoolSize(500);
        taskExecutor.setThreadNamePrefix("async-threadpool2-");
        taskExecutor.initialize();
        return taskExecutor;
    }

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return new AsyncUncaughtExceptionHandler() {
            @Override
            public void handleUncaughtException(Throwable throwable, Method method, Object... objects) {
                logger.error("AsyncUncaughtExceptionHandler:", throwable);
                System.out.println("method:" + method.getName());
                System.out.println("objects:" + Arrays.asList(objects));
            }
        };
    }

}

以上的使用方式都是在项目中常用的,个人觉得就是在默认线程池上可能会产生无解,其他使用从注释上都能非常直观的看到使用方式,不得不佩服作者的注释写的真好。

以上使用到的代码已经提交到Async工程中

SpringBootLearning是对springboot学习与研究项目,是根据实际项目的形式对进行配置与处理,欢迎star与fork。
[oschina 地址]
http://git.oschina.net/cmlbeliever/SpringBootLearning
[github 地址]
https://github.com/cmlbeliever/SpringBootLearning

猜你喜欢

转载自blog.csdn.net/believer123/article/details/80849728