Springboot中@Async的使用及掌握自定义线程池

576 阅读4分钟

前言

异步编程在企业级开发中是一种常用的手段,常用于解决同步编程下,调用时间过长的问题。流程通常是 “优先返回系统响应结果,将非核心逻辑解耦,放到后续处理”。在Springboot中,提供了天然的异步任务处理方法,在需要用到异步编程时,可以考虑该方法。下面,将详细介绍这种方法。

异步任务的使用方法

① 使用@Async注解开启异步任务之前,需要使用@EnableAsync注解开启异步支持。以下,是一个开启异步支持的Springboot启动类。

@SpringBootApplication(scanBasePackages = {"com.stady.async", "com.stady.config"})
@EnableAsync
@EnableConfigurationProperties({ThreadPoolConfig.class})
public class ApplicationRunner {

    public static void main(String[] args) {
        SpringApplication.run(ApplicationRunner.class, args);
        System.out.println("Springboot application run success.");
    }

}

② 在Springboot配置文件中,配置线程池属性

spring:
  task:
    execution:
      pool:
        allow-core-thread-timeout: true #是否允许核心线程超时
        core-size: 3 #核心线程池大小
        max-size: 5 #线程池最大大小
        queue-capacity: 10 #等待队列容量
        keep-alive: 60s #超时时间
  • spring.task.execution.pool.core-size:核心线程数,默认为8

  • spring.task.execution.pool.max-size:线程池的最大线程数,默认为int最大值

  • spring.task.execution.pool.keep-alive:线程终止前允许保持空闲的时间

  • spring.task.execution.pool.queue-capacity:线程等待队列,默认为int最大值

  • spring.task.execution.pool.allow-core-thread-timeout:是否允许核心线程超时

③ 接下来,编写异步任务执行类,并在异步方法上,标注@Async注解。

@Component
public class AsyncTask {

    @Async
    public CompletableFuture asyncMethodA() {
        long start = System.currentTimeMillis();
        try {
            // 模拟异步重试流程
            System.out.println("异步任务A开始执行");
            Thread.sleep(1000);
        } catch (Exception e) {
            System.out.println("异步任务A处理异常");
        }
        long end = System.currentTimeMillis();
        long all = end - start;
        System.out.println("异步任务A执行完成,耗時:" + all);
        return CompletableFuture.completedFuture("ok");
    }

}

④ 最后,在测试类中,对该异步任务进行测试。(为了看到异步处理过程,在此我使用join方法阻塞异步处理结果)

@RunWith(SpringRunner.class)
// 这里的ApplicationRunner.class为自己的启动类
@SpringBootTest(classes = ApplicationRunner.class)
public class AsyncTest {

    @Resource
    private AsyncTask asyncTask;

    @Test
    public void asyncTaskTest() {
        long start = System.currentTimeMillis();
        CompletableFuture task1 = asyncTask.asyncMethodA();
        CompletableFuture task2 = asyncTask.asyncMethodB();
        CompletableFuture task3 = asyncTask.asyncMethodC();
        CompletableFuture.allOf(task1, task2, task3).join();
        long end = System.currentTimeMillis();
        long time = end - start;
        System.out.println("all task ok. 耗时:" + time);
    }
    
}

测试结果如下

QQ截图20240622170447.png

到此,Springboot中异步任务的简单使用就结束了。接下来,我们来讨论一下如何自定义线程池

自定义线程池

在实际的企业开发中,业务场景通常多且杂,会有多个业务使用到异步任务。若这些异步任务全都使用一个线程池,通常是效率低下的,较耗资源的,还可能引发严重的问题。所以,正确的做法是,针对不同业务场景编写不同的线程池,来达到资源利用最大化。接下来,将讲述如何自定义一个线程池并使用。 ① 编写一个线程池配置类,如下

@ConfigurationProperties(prefix = "my.task.pool")
public class ThreadPoolConfig {

    private int corePoolSize;

    private int maxPoolSize;

    private int waitQueueSize;

    private int keepAliveSeconds;

    public int getCorePoolSize() {
        return corePoolSize;
    }

    public void setCorePoolSize(int corePoolSize) {
        this.corePoolSize = corePoolSize;
    }

    public int getMaxPoolSize() {
        return maxPoolSize;
    }

    public void setMaxPoolSize(int maxPoolSize) {
        this.maxPoolSize = maxPoolSize;
    }

    public int getWaitQueueSize() {
        return waitQueueSize;
    }

    public void setWaitQueueSize(int waitQueueSize) {
        this.waitQueueSize = waitQueueSize;
    }

    public int getKeepAliveSeconds() {
        return keepAliveSeconds;
    }

    public void setKeepAliveSeconds(int keepAliveSeconds) {
        this.keepAliveSeconds = keepAliveSeconds;
    }
}

别忘记启动类上的@EnableConfigurationProperties注解

@EnableConfigurationProperties({ThreadPoolConfig.class})

② 在application.yaml文件中,对其进行配置

my:
  task:
    pool:
      corePoolSize: 1
      maxPoolSize: 5
      waitQueueSize: 10
      keepAliveSeconds: 60

③ 现在,轮到自定义线程池了

@Configuration("myThreadPool")
@EnableAsync
public class MyThreadPool {

    @Resource
    private ThreadPoolConfig threadPoolConfig;

    @Bean
    public Executor asyncTaskPool() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(threadPoolConfig.getCorePoolSize());
        executor.setMaxPoolSize(threadPoolConfig.getMaxPoolSize());
        executor.setKeepAliveSeconds(threadPoolConfig.getKeepAliveSeconds());
        executor.setAllowCoreThreadTimeOut(true);
        executor.setQueueCapacity(threadPoolConfig.getWaitQueueSize());
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
        executor.initialize();
        return executor;
    }
}

在使用时,需要再@Async注解后,加上线程池的名称,以区分使用不同线程池

@Async("asyncTaskPool")
public CompletableFuture asyncMethodA() {
    long start = System.currentTimeMillis();
    try {
        // 模拟异步重试流程
        System.out.println("异步任务A开始执行");
        Thread.sleep(1000);
    } catch (Exception e) {
        System.out.println("异步任务A处理异常");
    }
    long end = System.currentTimeMillis();
    long all = end - start;
    System.out.println("异步任务A执行完成,耗時:" + all);
    return CompletableFuture.completedFuture("ok");
}

完成自定义线程池的配置之后,uu们可以自己测试一下,看是否生效。

注意

不要在同一个类中调用异步任务

可以将异步任务单独写一个类,之后在其他类中进行使用

不要忘记配置线程池

如果没有配置线程池,很可能会导致OOM,引起严重后果

这篇文章到这里就结束了,有问题和意见的uu可以在评论区留下自己的见解。大家一起探讨一下。0.0

参考文献