SpringBoot 中使用异步线程池 @EnableAsync @Async

提示: 对 Java 线程池 ThreadPoolExecutor 有所了解的道友阅读本篇更容易。

目录

一、 案例展示

二、 @EnableAsync 和 @Async

三、 查看源码中的线程池默认配置


一、 案例展示

在项目的实际应用中,很多时候一个请求都是在同一个线程中运行的。但有时候可能需要异步,也就是一个请求可能存在多个线程。

                                 

在Spring中, 存在一个接口叫做 AsyncConfigurer,  这是一个可配置异步线程池的接口: 

public interface AsyncConfigurer {
    @Nullable
    default Executor getAsyncExecutor() {
        return null;
    }

    @Nullable
    default AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return null;
    }
}

  它有两个方法, getAsyncExecutor() 和 getAsyncUncaughtExceptionHandler() 。 

   1. getAsyncExecutor() 方法返回一个自定义的线程池, 开启异步时, 线程池就会提供空闲的线程来执行异步任务;

   2. getAsyncUncaughtExceptionHandler(), 因为线程中的业务逻辑可能会抛出异常, 所以这个方法用来定义一个处理异常的处理器;

   在实际使用中, 我们只需要写一个自己的类,实现 AsyncConfigurer 接口, 重写这两个方法就好了。

   Demo:

   1.  我们定义自己的异步线程池配置类 AsyncConfig , 实现 AsyncConfigurer 接口,重写这两个方法。 

   2.  在 getAsyncExecutor() 方法中, 定义想要用到的线程池类型和参数,此处我们展示的是ThreadPoolTaskExecutor, 这也是最常用的线程池类型;

   3.  在 getAsyncUncaughtExceptionHandler() 方法中, 返回自己定义的异常处理器;

   4.  该类上面添加 @EnableAsync 注解, 这样Spring就会开启异步可用;

@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {

    /**自定义的异常处理器*/
    @Autowired
    private AsyncExceptionHandler asyncExceptionHandler;

    @Override
    public Executor getAsyncExecutor() {
        // 定义线程池
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        // 设置核心线程数
        taskExecutor.setCorePoolSize(5);
        // 设置最大线程数
        taskExecutor.setMaxPoolSize(10);
        // 设置队列的大小
        taskExecutor.setQueueCapacity(2000);
        // 初始化线程池
        taskExecutor.initialize();
        return taskExecutor;
    }

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return asyncExceptionHandler;
    }
}

  自定义的异常处理类Demo:

  1.  需要实现 AsyncUncaughtExceptionHandler 接口, 重写里面的 handleUncaughtException() 方法;

  2.  根据实际项目需要, 我们可以在里面处理异常, 比如例子中为该方法添加了事务的支持, 并将错误信息放入log;

@Component
public class AsyncExceptionHandler implements AsyncUncaughtExceptionHandler {

    private final Logger logger = LoggerFactory.getLogger(AsyncExceptionHandler.class);

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void handleUncaughtException(Throwable throwable, Method method, Object... objects) {
        logger.info("method:{}, params:{}, exception:{}", method.getName(), objects, throwable.getMessage());
    }
}

下面让我们看看如何在项目中运用它:

 定义Service接口,并实现其中的 testAsync 方法:

1. 被@Async标注的方法,称为异步方法,会在独立的线程中被执行。调用它的线程,无需等待它结束,会继续进行别的操作;

public interface AsyncService {
    /**
     * 测试异步线程池
     */
    void testAsync() throws InterruptedException;
}
@Service
public class AsyncServiceImpl implements AsyncService {

    private final Logger logger = LoggerFactory.getLogger(AsyncServiceImpl.class);
    /**
     * 使用注解 @Async, 声明使用异步调用
     */
    @Override
    @Async
    public void testAsync() throws InterruptedException{
        Thread.sleep(3000);
        logger.info("Service中线程名称:{}", Thread.currentThread().getName());
        int i = 1/0;
    }
}

  在Controller中调用它:

@RestController(value = "/async")
public class AsyncController {

    private final Logger logger = LoggerFactory.getLogger(AsyncController.class);

    @Autowired
    private AsyncService asyncService;

    @GetMapping(value = "/test")
    public String testAsync() throws InterruptedException {
        logger.info("Controller中线程名称:{}", Thread.currentThread().getName());
        //异步调用
        asyncService.testAsync();
        logger.info("Service中延迟了3S, 谁先打印出来呢");
        return "hello world";
    }

}


打印结果如下:

2020-06-08 15:41:01,372 INFO (AsyncController.java:23)- Controller中线程名称:http-nio-8888-exec-3
2020-06-08 15:41:01,373 INFO (AsyncController.java:25)- Service中延迟了3S, 谁先打印出来呢
2020-06-08 15:41:04,375 INFO (AsyncServiceImpl.java:24)- Service中线程名称:ThreadPoolTaskExecutor-2
2020-06-08 15:41:04,378 INFO (AsyncExceptionHandler.java:20)- method:testAsync, params:[], exception:/ by zero

 我们可以看到:

1.  虽然 "Service中延迟了3S, 谁先打印出来呢" 这句话在 asyncService.testAsync(); 之后执行, 但仍然先打印出来了。 因为这里已经是异步调用了;

2. Controller 和 Service 中打印出来的线程名称, 已经不是同一个线程了;

3. 我们在 Service 中写了1/0 这样会导致异常的代码,也被我们定义的异常处理器成功捕获,并按照我们想要的格式出现在了log中;

 二、 @EnableAsync 和 @Async

 @EnableAsync:

        1.  Spring 中有很多 @EnableXX + @XX 的注解, 用于实现对某功能的支持。 比如 @EnableCaching+@Cache 提供缓存功能, @EnableScheduling+@Scheduled 提供定时功能。  同样, 这里的 @EnableAsync 用于开启异步功能, @Async用于表示具体哪些方法支持异步调用。 

      2.  @EnableAsync, 这个注解是加在类上的。  应用中,我们可以把它放在SpringBoot的启动类上,表示我们的项目开启异步;

@Async:

      1. @Async, 这个注解既可以加在类上,也可以加载方法上。 加在类上表明这个类中的所有方法都支持异步调用, 加在方法上表明这单个方法支持异步调用;

      2. 在上面的demo中, 被 @Async 修饰的方法是没有返回值的, 在实际应用中,若想使用带返回值的方法, 只需稍稍修改一下便好, 比如:      

@Override
@Async
public Future<String> testAsync() throws InterruptedException{
    Thread.sleep(3000);
    logger.info("Service中线程名称:{}", Thread.currentThread().getName());
    return new AsyncResult<String>("abc");
}

在方法定义中,

   2.1 使用 Future<T> 作为返回值 ,  这个就是java concurrent 包下的Future接口 。  

   2.2 return 中返回一个 AsyncResult<T>, AsyncResult是Spring中的类, 它实现了ListenableFuture<V>这个接口,而ListenableFuture<V>这个接口也是继承了 Future。 源码如下    

public class AsyncResult<V> implements ListenableFuture<V> 
public interface ListenableFuture<T> extends Future<T>

   2.3 然后在调用该方法的代码中,写上:  

Future<String> future = asyncService.testAsync();

  .....

     future.get() 

  .....

     不过future.get() 方法会阻塞主线程, 让本来的异步处理又回到了同步状态。 

  

三、 查看源码中的线程池默认配置

熟悉Java 中 ThreadPoolExecutor 的道友知道, 定义一个线程池,需要的参数可不止这几个, 我们看一下JDK源码中ThreadPoolExecutor的构造函数:

ThreadPoolExecutor(int corePoolSize,    // 核心线程数
                   int maximumPoolSize, // 最大线程数
                   long keepAliveTime,  // 线程经过多久空闲时间会被回收
                   TimeUnit unit,       // 时间单位
                   BlockingQueue<Runnable> workQueue,   // 工作队列
                   ThreadFactory threadFactory,         // 线程工程
                   RejectedExecutionHandler handler) {  // 拒绝策略

 可以发现这里有7个参数, 而我们在上面的Demo中只定义了其中几个, 那Spring肯定是用来默认参数进行填充的,再来看一下我们上述自定义的线程池代码:

    @Override
    public Executor getAsyncExecutor() {
        // 定义线程池
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        // 设置核心线程数
        taskExecutor.setCorePoolSize(5);
        // 设置最大线程数
        taskExecutor.setMaxPoolSize(10);
        // 设置队列的大小
        taskExecutor.setQueueCapacity(2000);
        // 初始化线程池
        taskExecutor.initialize();
        return taskExecutor;
    }

其中有个方法叫:  initialize()。 我们点击去看一下, 会进入到一个叫 ExecutorConfigurationSupport 的类中,而我们例子中使用的 ThreadPoolTaskExecutor 正是这个类的子类。 

 在ExecutorConfigurationSupport 类中有如下定义:

private ThreadFactory threadFactory = this;
private RejectedExecutionHandler rejectedExecutionHandler = new AbortPolicy();

public void initialize() {
    if (this.logger.isInfoEnabled()) {
        this.logger.info("Initializing ExecutorService" + (this.beanName != null ? " '" + this.beanName + "'" : ""));
    }

    if (!this.threadNamePrefixSet && this.beanName != null) {
        this.setThreadNamePrefix(this.beanName + "-");
    }

    this.executor = this.initializeExecutor(this.threadFactory, this.rejectedExecutionHandler);
}

        可以看到如果我们不设置自己的 threadFactory 和 rejectedExecutionHandler, 便会用默认的进行填充。可以看到这里的拒绝策略默认为 AbortPolicy().  也就是一旦任务队列满了之后,新的任务会被直接丢弃, 并抛出 RejectedExecutionException。 注: Java 中自己创建线程池,默认的拒绝策略也是这个。      

      接着这里的 initialize() 方法会接着调用 initializeExecutor() 方法继续进行初始化操作, 那我们就接着扒拉下去,看看这个方法:

private int corePoolSize = 1;
private int maxPoolSize = 2147483647;
private int keepAliveSeconds = 60;
private int queueCapacity = 2147483647;
protected ExecutorService initializeExecutor(ThreadFactory threadFactory, RejectedExecutionHandler rejectedExecutionHandler) {
    BlockingQueue<Runnable> queue = this.createQueue(this.queueCapacity);
    ThreadPoolExecutor executor;
    if (this.taskDecorator != null) {
        executor = new ThreadPoolExecutor(this.corePoolSize, this.maxPoolSize, (long)this.keepAliveSeconds, TimeUnit.SECONDS, queue, threadFactory, rejectedExecutionHandler) {
            public void execute(Runnable command) {
                Runnable decorated = ThreadPoolTaskExecutor.this.taskDecorator.decorate(command);
                if (decorated != command) {
                    ThreadPoolTaskExecutor.this.decoratedTaskMap.put(decorated, command);
                }

                super.execute(decorated);
            }
        };
    } else {
        executor = new ThreadPoolExecutor(this.corePoolSize, this.maxPoolSize, (long)this.keepAliveSeconds, TimeUnit.SECONDS, queue, threadFactory, rejectedExecutionHandler);
    }

    if (this.allowCoreThreadTimeOut) {
        executor.allowCoreThreadTimeOut(true);
    }

    this.threadPoolExecutor = executor;
    return executor;
}
protected BlockingQueue<Runnable> createQueue(int queueCapacity) {
    return (BlockingQueue)(queueCapacity > 0 ? new LinkedBlockingQueue(queueCapacity) : new SynchronousQueue());
}

 通过上述源码,我们可以进一步发现:

1.  如果queueCapacity默认大小是2147483647, 即Integer.MAX 值。 如果我们自己定义的这个值大于0, 返回的队列实际是一个LinkedBlockingQueue;

2. 默认corePoolSize 是1, maxPoolSize也是2147483647 , 和queueCapacity默认大小是一样的。 试想一下若是我们不自己定义这个maxPoolSize, 真若是同时来了很多task, 便会创建大量的线程, 会不会直接搞挂我们的服务呢,,,有待测试。

3. 默认线程空闲时间超过60秒后, 会被回收;

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值