【SpringBoot】异步调用的使用和拓展

通常在开发过程中,会遇到一个方法是和实际业务无关的,没有紧密性的。比如记录日志信息等业务。这个时候正常就是启一个新线程去做一些业务处理,让主线程异步的执行其他业务

异步请求与异步调用的区别

  • 两者的使用场景不同,异步请求用来解决并发请求对服务器造成的压力,从而提高对请求的吞吐量;而异步调用是用来做一些非主线流程且不需要实时计算和响应的任务,比如同步日志到kafka中做日志分析等。
  • 异步请求是会一直等待response响应的,需要返回结果给客户端的;而异步调用我们往往会马上返回给客户端响应,完成这次整个的请求,至于异步调用的任务后台自己慢慢跑就行,客户端不会关心。

前言

通常,在Spring程序中做一个耗时的操作(例如调用其他外部模块),一般会通过异步的方式执行。
一版有这2种方法:

  1. 创建原生线程池ThreadPoolExecutor,提交任务执行
  2. 使用@Async注解,标注在需要异步执行的方法上

一.Spring实现的线程池

Spring实现的异步线程池,都实现了TaskExecutor接口,其实质是java.util.concurrent.Executor(TaskExecutor继承了Executor)。

名字 描述
SimpleAsyncTaskExecutor 不是真的线程池,这个类不重用线程,每次调用都会创建一个新的线程。但它有一个最大同时执行线程数的限制。(@Aysnc默认线程池
SyncTaskExecutor 这个类没有实现异步调用,只是一个同步操作,任务的执行是在主线程中,不会启动新的线程来执行提交的任务。主要使用在没有必要使用多线程的情况,如较为简单的测试用例。
ConcurrentTaskExecutor Executor的适配类,不推荐使用。如果ThreadPoolTaskExecutor不满足要求时,才用考虑使用这个类
SimpleThreadPoolTaskExecutor 它是Quartz中SimpleThreadPool的一个实现,用于监听Spring生命周期回调事件。它主要使用在需要一个线程池来被Quartz和非Quartz中的对象同时使用,才需要使用此类
WorkManagerTaskExecutor 它实现了CommonJ中的WorkManager接口,是在Spring中使用CommonJ的WorkManager时的核心类。(我也没有用过)
ThreadPoolTaskExecutor 最常使用推荐。 其实质是对java.util.concurrent.ThreadPoolExecutor的包装

Spring线程池配置方式
可以使用SpringBoot默认的线程池,不过一般我们会自定义线程池(因为比较灵活),配置方式有:

  1. 使用 xml文件配置的方式
  2. 使用Java代码结合@Configuration+@Async进行配置(推荐使用

Spring中启用@Async方式

  1. 基于Java配置的启用方式:
@Configuration
@EnableAsync
public class SpringAsyncConfig {
    
    // ... }

  1. SpringBoot启用启动方式
@EnableAsync
@SpringBootApplication
public class SpringAsyncApplication {
    
    
    public static void main(String[] args) {
    
    
        SpringApplication.run(SpringAsyncApplication.class, args);
    }
}

@Async应用默认线程池
不指定线程池的名称。看源码发现@Async的默认线程池为SimpleAsyncTaskExecutor

该线程池默认来一个任务创建一个线程,若系统中不断的创建线程,最终会导致系统占用内存过高,引发OutOfMemoryError错误

  • 针对线程创建问题,SimpleAsyncTaskExecutor提供了限流机制,通过concurrencyLimit属性来控制开关,当concurrencyLimit>=0时开启限流机制,默认关闭限流机制即concurrencyLimit=-1,当关闭情况下,会不断创建新的线程来处理任务。基于默认配置,SimpleAsyncTaskExecutor并不是严格意义的线程池,达不到线程复用的功能。
    springboot-@Async默认线程池导致OOM问题

二.SpringBoot中异步调用步骤

1. 在启动类 或 线程池配置类(@Configuration)中上加上@EnableAsync注解开启异步操作

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

2.Java代码结合@Configuration+@Bean注解的配置配置线程池

@Configuration
public class ThreadPoolExecutorConfigure {
    
    
  @Bean(name = "taskExecutorAsync")
    public Executor taskExecutor() {
    
    
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10);//核心线程数
        executor.setMaxPoolSize(10);//非核心线程数
        executor.setQueueCapacity(200);//队列大小
        executor.setKeepAliveSeconds(60);//非核心线程空闲时间(秒)
        executor.setThreadNamePrefix("taskExecutor1-");//线程浅醉
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());//拒绝策略-丢弃
        //该方法就是这里的关键,用来设置线程池关闭的时候等待所有任务都完成再继续销毁其他的Bean,这样这些异步任务的销毁就会先于Redis线程池的销毁。
        executor.setWaitForTasksToCompleteOnShutdown(true);
        //该方法用来设置线程池中任务的等待时间,如果超过这个时候还没有销毁就强制销毁,以确保应用最后能够被关闭,而不是阻塞住。
        executor.setAwaitTerminationSeconds(60);
        return executor;
    }
}

3.在方法上使用@Async注解

@Component
public class AsyncCallback {
    
    
    /**
     * 最简单的异步调用
     */
    @Async("taskExecutorAsync")
    public void testAsync(int num) throws InterruptedException {
    
    
        TimeUnit.SECONDS.sleep(2);
        System.out.println(Thread.currentThread().getName() + "=》测试异步调用"+num);
    }
    /**
     * 同步调用方法
     */
    public void testSync(int num) {
    
    
        System.out.println(Thread.currentThread().getName() + "=》测试同步调用"+num);

    }
}
  • 如果@Async注解没有指定executor,则会去寻找Spring容器的名为taskExecutor的executor,如果没有,则会导致执行异常

4.测试效果

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class AsyncCallbackTest {
    
    
    @Autowired
    AsyncCallback asyncCallback;

    @Test
    public  void main() throws InterruptedException {
    
    
        //创建5个异步调用任务
        for (int i = 0; i<5 ;i++) {
    
    
            asyncCallback.testAsync(i);
        }
        
        //调用3个同步方法
        asyncCallback.testSync(1);
        asyncCallback.testSync(2);
        asyncCallback.testSync(3);
    }
}

执行结果:
在这里插入图片描述

坑: @Async标注方法的调用者 不能和 @Async标注方法同一类中不然不会起作用!!!!

三.@Async

用于标注某个方法是需要异步处理的或某个类里面的所有方法都是需要异步处理的。

  • 适用于处理log、发送邮件、短信……等。

应用范围:

  • 类:表示这个类中的所有方法都是异步的
  • 方法:表示这个方法是异步的,如果类也注解了,则以这个方法的注解为准

3.1.@Async基本使用

异步的方法有3种

  1. 无返回值的异步调用
  2. 带参的异步调用
  3. 有返回值的异步调用
@Component
public class AsyncDemo {
    
    
    /**
     * 最简单的异步调用,无返回值
     */
    @Async
    public void asyncInvokeSimplest() {
    
    
        log.info("asyncSimplest");
    }
    /**
     * 带参数的异步调用 异步方法可以传入参数
     */
    @Async
    public void asyncInvokeWithParameter(String s) {
    
    
        log.info("asyncInvokeWithParameter, parementer={}", s);
    }

    /**
     * 异步调用返回Future
     */
    @Async
    public Future<String> asyncInvokeReturnFuture(int i) {
    
    
        log.info("asyncInvokeReturnFuture, parementer={}", i);
        Future<String> future;
        try {
    
    
            Thread.sleep(1000 * 1);
            future = new AsyncResult<String>("success:" + i);
        } catch (InterruptedException e) {
    
    
            future = new AsyncResult<String>("error");
        }
        return future;
    }
}

测试

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class AsyncCallbackTest {
    
    
    @Autowired
    AsyncDemo asyncDemo;
    
    @Test
    public void main2() throws ExecutionException, InterruptedException {
    
    
        //最简单的异步调用,返回值为void
        asyncDemo.asyncInvokeSimplest();
        //带参数的异步调用 异步方法可以传入参数
        asyncDemo.asyncInvokeWithParameter("test");
        //异步调用返回Future
        Future<String> future = asyncDemo.asyncInvokeReturnFuture(100);
        System.out.println(future.get());
    }
}

执行结果:
在这里插入图片描述

3.2.@Async自定义线程池

自定义线程池,可对系统中线程池实现更新精细的控制, 方便调整线程池大小, 执行异常控制

如下:

  1. 实现AsyncConfigurer(异步配置器)接口
  2. 继承AsyncConfigurerSupport(异步配置器)类(实现了AsyncConfigurer接口)
  3. 配置自定义的ThreadPoolTaskExecutor 替换掉@Async默认的线程池

通过查看Spring源码关于@Async的默认调用规则,会优先查询源码中实现AsyncConfigurer这个接口的类AsyncConfigurerSupport。但AsyncConfigurerSupport默认配置的线程池和异步处理方法均为空,所以,无论是继承AsyncConfigurerSupport类或者重新实现AsyncConfigurer接口,都需要重新实现 public Executor getAsyncExecutor()方法,指定一个线程池。
在这里插入图片描述

注意事项
无论是实现实现AsyncConfigurer接口或者继承AsyncConfigurerSupport类来对线程池进行配置,SpringBoot项目中都只允许存在一个异步配置器,如果存在两个异步配置器则会报错:
在这里插入图片描述

3.2.1.实现AsyncConfigurer接口指定线程池及异常处理

  1. 实现AsyncConfigurer接口重写相关方法
  2. 实现AsyncUncaughtExceptionHandler接口,处理异步方法抛出异常
  3. 启动类开启@EnableAsync启动异步调用

异步方法类还是使用上面的AsyncExceptionDemo类,然后实现AsyncConfigurer接口

/**
 * 通过实现AsyncConfigurer自定义异常线程池,包含异常处理
 */
@Service
public class MyAsyncConfigurer implements AsyncConfigurer {
    
    
    @Override
    public Executor getAsyncExecutor() {
    
    
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(1);
        executor.setMaxPoolSize(1);
        executor.setWaitForTasksToCompleteOnShutdown(true);
        executor.setAwaitTerminationSeconds(60 * 15);
        executor.setThreadNamePrefix("MyAsync-");
        //该方法就是这里的关键,用来设置线程池关闭的时候等待所有任务都完成再继续销毁其他的Bean,这样这些异步任务的销毁就会先于Redis线程池的销毁。
        executor.setWaitForTasksToCompleteOnShutdown(true);
        //该方法用来设置线程池中任务的等待时间,如果超过这个时候还没有销毁就强制销毁,以确保应用最后能够被关闭,而不是阻塞住。
        executor.setAwaitTerminationSeconds(60);
        executor.initialize();
        return threadPool;
    }
    /**
     * 自定义异常处理器
     *
     * @return
     */
    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
    
    
        return new MyAsyncExceptionHandler();
    }
    /**
     * 自定义异常处理类
     */
    class MyAsyncExceptionHandler implements AsyncUncaughtExceptionHandler {
    
    

        @Override
        public void handleUncaughtException(Throwable throwable, Method method, Object... obj) {
    
    
            System.out.println("Exception message - " + throwable.getMessage());
            System.out.println("Method name - " + method.getName());
            for (Object param : obj) {
    
    
                System.out.println("Parameter value - " + param);
            }
        }
    }
}

测试

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class AsyncCallbackTest {
    
    
    @Autowired
    AsyncExceptionDemo asyncExceptionDemo;

    @Test
    public void main3() throws ExecutionException, InterruptedException {
    
    
        //最简单的异步调用,返回值为void
        asyncExceptionDemo.asyncInvokeSimplest();
        //带参数的异步调用 异步方法可以传入参数
        asyncExceptionDemo.asyncInvokeWithException("test");
        //异步调用返回Future
        Future<String> future = asyncExceptionDemo.asyncInvokeReturnFuture(100);
        System.out.println(future.get());
    }
}

执行结果
在这里插入图片描述

3.2.2.继承AsyncConfigurerSupport类指定线程池及异常处理

AsyncConfigurerSupport类底层也是实现了 AsyncConfigurer接口,但没有做任何实现,因此也需要对getAsyncExecutor()getAsyncUncaughtExceptionHandler()方法进行重写

/**
 * 通过实现AsyncConfigurer自定义异常线程池,包含异常处理
 */
@Service
public class MyAsyncConfigurerSupport extends AsyncConfigurerSupport {
    
    
    @Override
    public Executor getAsyncExecutor() {
    
    
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(1);
        executor.setMaxPoolSize(1);
        executor.setWaitForTasksToCompleteOnShutdown(true);
        executor.setAwaitTerminationSeconds(60 * 15);
        executor.setThreadNamePrefix("MyAsyncSupport-");
        //该方法就是这里的关键,用来设置线程池关闭的时候等待所有任务都完成再继续销毁其他的Bean,这样这些异步任务的销毁就会先于Redis线程池的销毁。
        executor.setWaitForTasksToCompleteOnShutdown(true);
        //该方法用来设置线程池中任务的等待时间,如果超过这个时候还没有销毁就强制销毁,以确保应用最后能够被关闭,而不是阻塞住。
        executor.setAwaitTerminationSeconds(60);
        executor.initialize();
        return threadPool;
    }

    /**
     * 自定义异常处理器
     *
     * @return
     */
    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
    
    
        return new AsyncConfigurerSupportExceptionHandler();
    }

    /**
     * 自定义异常处理类
     */
    class AsyncConfigurerSupportExceptionHandler implements AsyncUncaughtExceptionHandler {
    
    

        @Override
        public void handleUncaughtException(Throwable throwable, Method method, Object... obj) {
    
    
            System.out.println(Thread.currentThread().getName()+"=>Exception message - " + throwable.getMessage());
            System.out.println(Thread.currentThread().getName()+"=>Method name - " + method.getName());
            for (Object param : obj) {
    
    
                System.out.println(Thread.currentThread().getName()+"=>Parameter value - " + param);
            }
        }
    }
}

3.2.3.自定义的ThreadPoolTaskExecutor替换掉@Async的默认线程池

由于AsyncConfigurer的默认线程池在源码中为空,Spring通过beanFactory.getBean(TaskExecutor.class)先查看是否有线程池,未配置时,通过beanFactory.getBean(DEFAULT_TASK_EXECUTOR_BEAN_NAME, Executor.class),又查询是否存在默认名称为taskExecutor的线程池

DEFAULT_TASK_EXECUTOR_BEAN_NAME的值为在这里插入图片描述

*所以我们定义@Bean名称为taskExecutor,生成一个默认线程池。

  • 也可不指定线程池的名称,声明一个线程池,本身底层是基于TaskExecutor.class便可。*
@EnableAsync
@Configuration
public class TaskPoolConfig {
    
    
     @Bean(name = AsyncExecutionAspectSupport.DEFAULT_TASK_EXECUTOR_BEAN_NAME)
     public Executor taskExecutor() {
    
    
         ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
          //核心线程池大小
         executor.setCorePoolSize(10);
         //最大线程数
         executor.setMaxPoolSize(20);
         //队列容量
         executor.setQueueCapacity(200);
        //活跃时间
         executor.setKeepAliveSeconds(60);
         //线程名字前缀
         executor.setThreadNamePrefix("taskExecutor-");
         //拒绝策略-丢弃
         executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
         //该方法就是这里的关键,用来设置线程池关闭的时候等待所有任务都完成再继续销毁其他的Bean,这样这些异步任务的销毁就会先于Redis线程池的销毁。
         executor.setWaitForTasksToCompleteOnShutdown(true);
         //该方法用来设置线程池中任务的等待时间,如果超过这个时候还没有销毁就强制销毁,以确保应用最后能够被关闭,而不是阻塞住。
         executor.setAwaitTerminationSeconds(60);
         return executor;
    }
 }

3.3.@Async的异常处理

在调用方法时,可能出现方法中抛出异常的情况。在异步中主要有有两种异常处理方法:

方式a:对于方法返回值是Futrue的异步方法

  1. 在调用future的get()方法时捕获异常
  2. 异步方法内中直接捕获异常

方式:.对于返回值是void的异步方法:可以实现AsyncConfigurer接口,也可以继承AsyncConfigurerSupport类来实现,在重写方法getAsyncExecutor()中创建线程池的时候,必须先初始化线程池,不然在调用时会报线程池未初始化的异常

初始化有两方式

  1. 使用 executor.initialize()
  2. @Configuration+@Bean加载一个ThreadPoolTaskExecutor 到Spring容器中供调用

方式a:

@Component
public class AsyncExceptionDemo {
    
    
    /**
     * 最简单的异步调用,返回值为void
     */
    @Async
    public void asyncInvokeSimplest() {
    
    
        System.out.println("asyncSimplest");
    }
    /**
     * 带参数的异步调用 异步方法可以传入参数
     *  对于返回值是void,异常会被AsyncUncaughtExceptionHandler处理掉
     * @param s
     */
    @Async
    public void asyncInvokeWithException(String s) {
    
    
        System.out.println("asyncInvokeWithParameter, parementer="+ s);
        throw new IllegalArgumentException(s);
    }
    /**
     * 异步调用返回Future
     *  对于返回值是Future,不会被AsyncUncaughtExceptionHandler处理,需要我们在方法中捕获异常并处理
     *  或者在调用方在调用Future.get时捕获异常进行处理
     * 
     * @param i
     * @return
     */
    @Async
    public Future<String> asyncInvokeReturnFuture(int i) {
    
    
        System.out.println("asyncInvokeReturnFuture, parementer="+ i);
        Future<String> future;
        try {
    
    
            Thread.sleep(1000);
            future = new AsyncResult<String>("success:" + i);
            throw new IllegalArgumentException("a");
        } catch (InterruptedException e) {
    
    
            future = new AsyncResult<String>("error");
        } catch(IllegalArgumentException e){
    
    
            future = new AsyncResult<String>("error-IllegalArgumentException");
        }
        return future;
    }
}

测试

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class AsyncCallbackTest {
    
    
    @Autowired
    AsyncExceptionDemo asyncExceptionDemo;

    @Test
    public void main3() throws ExecutionException, InterruptedException {
    
    
        //最简单的异步调用,返回值为void
        asyncExceptionDemo.asyncInvokeSimplest();
        //带参数的异步调用 异步方法可以传入参数
        asyncExceptionDemo.asyncInvokeWithException("test");
        //异步调用返回Future
        Future<String> future = asyncExceptionDemo.asyncInvokeReturnFuture(100);
        System.out.println(future.get());
    }
}

执行结果
在这里插入图片描述

方式b:
参照3.2.1和3.2.2的处理方式来同一处理就行了 =>即 实现AsyncConfigurer接口 或 继承AsyncConfigurerSupport类,重写 getAsyncUncaughtExceptionHandler() 方法,然后自定义返回AsyncUncaughtExceptionHandler对象统一处理异常

四.扩展ThreadPoolTaskExecutor

虽然@Async很方便,但是线程池当前的状态,有多少线程在执行,多少在队列中等待,我们并不知道,我们可以继承ThreadPoolTaskExecutor重写相关方法,在每次提交线程的时候都会将当前线程池的运行参数,打印出来,代码如下:

public class VisiableThreadPoolTaskExecutor extends ThreadPoolTaskExecutor {
    
    
    private static final Logger logger = LoggerFactory.getLogger(VisiableThreadPoolTaskExecutor.class);

    private void showThreadPoolInfo(String prefix){
    
    
        ThreadPoolExecutor threadPoolExecutor = getThreadPoolExecutor();

        if(null==threadPoolExecutor){
    
    
            return;
        }

        logger.info("{}, {},taskCount [{}], completedTaskCount [{}], activeCount [{}], queueSize [{}]",
                this.getThreadNamePrefix(),
                prefix,
                threadPoolExecutor.getTaskCount(),
                threadPoolExecutor.getCompletedTaskCount(),
                threadPoolExecutor.getActiveCount(),
                threadPoolExecutor.getQueue().size());
    }

    @Override
    public void execute(Runnable task) {
    
    
        showThreadPoolInfo("1. do execute");
        super.execute(task);
    }
    @Override
    public void execute(Runnable task, long startTimeout) {
    
    
        showThreadPoolInfo("2. do execute");
        super.execute(task, startTimeout);
    }
    @Override
    public Future<?> submit(Runnable task) {
    
    
        showThreadPoolInfo("1. do submit");
        return super.submit(task);
    }
    @Override
    public <T> Future<T> submit(Callable<T> task) {
    
    
        showThreadPoolInfo("2. do submit");
        return super.submit(task);
    }
    @Override
    public ListenableFuture<?> submitListenable(Runnable task) {
    
    
        showThreadPoolInfo("1. do submitListenable");
        return super.submitListenable(task);
    }
    @Override
    public <T> ListenableFuture<T> submitListenable(Callable<T> task) {
    
    
        showThreadPoolInfo("2. do submitListenable");
        return super.submitListenable(task);
    }
}

如上所示,showThreadPoolInfo方法中将任务总数、已完成数、活跃线程数,队列大小都打印出来了,然后重写了父类的execute、submit等方法,在里面调用showThreadPoolInfo方法,这样每次有任务被提交到线程池的时候,都会将当前线程池的基本情况打印到日志中;

初始化ThreadPoolTaskExecutor的时候实现改为new VisiableThreadPoolTaskExecutor()即可:如下:

@Configuration
public class VisiableThreadPoolTaskExecutorConfigure {
    
    
    @Bean(name = "asyncServiceExecutor")
    public Executor asyncServiceExecutor() {
    
    
        System.out.println("start asyncServiceExecutor");
        //使用VisiableThreadPoolTaskExecutor
        ThreadPoolTaskExecutor executor = new VisiableThreadPoolTaskExecutor();
        //配置核心线程数
        executor.setCorePoolSize(5);
        //配置最大线程数
        executor.setMaxPoolSize(5);
        //配置队列大小
        executor.setQueueCapacity(99999);
        //配置线程池中的线程的名称前缀
        executor.setThreadNamePrefix("async-service-");
        // rejection-policy:当pool已经达到max size的时候,如何处理新任务
        // CALLER_RUNS:不在新线程中执行任务,而是由调用者所在的线程来执行
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        //该方法就是这里的关键,用来设置线程池关闭的时候等待所有任务都完成再继续销毁其他的Bean,这样这些异步任务的销毁就会先于Redis线程池的销毁。
        executor.setWaitForTasksToCompleteOnShutdown(true);
        //该方法用来设置线程池中任务的等待时间,如果超过这个时候还没有销毁就强制销毁,以确保应用最后能够被关闭,而不是阻塞住。
        executor.setAwaitTerminationSeconds(60);
        //执行初始化
        executor.initialize();
        return executor;
    }
}

调用异步方法

@Component
public class VisiableThreadPoolTaskExecutorDemo {
    
    
    @Async("asyncServiceExecutor")
    public void asyncInvokeSimplest() throws InterruptedException {
    
    
        TimeUnit.MILLISECONDS.sleep(200);
        System.out.println(Thread.currentThread().getName() + "=>asyncInvokeSimplest");
    }
}

测试

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class AsyncCallbackTest {
    
    
    @Autowired
    VisiableThreadPoolTaskExecutorDemo visiableThreadPoolTaskExecutorDemo;
    @Test
    public void main5() throws InterruptedException, ExecutionException {
    
    
        //创建5个异步调用任务
        for (int i = 0; i < 100; i++) {
    
    
            visiableThreadPoolTaskExecutorDemo.asyncInvokeSimplest();
        }
    }
}

五.@Async之线程池的优雅关闭

//该方法就是这里的关键,用来设置线程池关闭的时候等待所有任务都完成再继续销毁其他的Bean,这样这些异步任务的销毁就会先于Redis线程池的销毁。
setWaitForTasksToCompleteOnShutdown(true)
//同时,这里还设置了 
//该方法用来设置线程池中任务的等待时间,如果超过这个时候还没有销毁就强制销毁,以确保应用最后能够被关闭,而不是阻塞住。        
setAwaitTerminationSeconds(60)
  • 如果不使用以上两个配置, 当线程池已经关闭时,但还有任务未执行完,会报异常

六.注意事项

如下方式会使@Async失效

  1. 异步方法使用static修饰
  2. 异步类没有使用@Component注解(或其他注解)导致Spring无法扫描到异步类
  3. 异步方法不能与被调用的异步方法在同一个类中

    在spring中像@Async和@Transactional、@cache等注解本质使用的是动态代理,其实Spring容器在初始化的时候Spring容器会将含有AOP注解的类对象“替换”为代理对象(简单这么理解),那么注解失效的原因就很明显了,就是因为调用方法的是对象本身而不是代理对象,因为没有经过Spring容器,那么解决方法也会沿着这个思路来解决。

  4. 类中需要使用Spring容器中的Bean,必须使用@Autowired@Resource等注解自动注入,不能自己手动new对象
  5. 如果使用SpringBoot框架必须在配置类或启动类中增加@EnableAsync或者注解

七.@Async部分重要源码解析(了解即可)

获取线程池方法
在这里插入图片描述
设置默认线程池defaultExecutor,默认是空的,当重新实现接口AsyncConfigurer的getAsyncExecutor()时,可以设置默认的线程池。
在这里插入图片描述在这里插入图片描述
寻找系统默认线程池
在这里插入图片描述
都没有找到项目中设置的默认线程池时,采用spring 默认的线程池
在这里插入图片描述

springboot通过AsyncConfigurer接口实现异步线程池自动化配置组件

猜你喜欢

转载自blog.csdn.net/qq877728715/article/details/106617892