Spring中的多线程使用(使用线程池 ThreadPoolTaskExecutor, CompletableFuture)

Spring 同时被 2 个专栏收录
54 篇文章 0 订阅
117 篇文章 0 订阅

SpringBoot应用中需要添加@EnableAsync注解,来开启异步调用,一般还会配置一个线程池,异步的方法交给特定的线程池完成,

1. 配置线程池

添加一个Configuration类, 代码如下:

@Configuration
@EnableAsync
public class ThreadConfig {

    @Bean("doSomethingExecutor")
    public Executor doSomethingExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 核心线程数:线程池创建时候初始化的线程数
        executor.setCorePoolSize(30);
        // 最大线程数:线程池最大的线程数,只有在缓冲队列满了之后才会申请超过核心线程数的线程
        executor.setMaxPoolSize(60);
        // 缓冲队列:用来缓冲执行任务的队列
        executor.setQueueCapacity(500);
        // 允许线程的空闲时间60秒:当超过了核心线程之外的线程在空闲时间到达之后会被销毁
        executor.setKeepAliveSeconds(60);
        // 线程池名的前缀:设置好了之后可以方便我们定位处理任务所在的线程池
        executor.setThreadNamePrefix("do-something-");
        // 缓冲队列满了之后的拒绝策略:由调用线程处理(一般是主线程)
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());
        executor.initialize();
        return executor;
    }
}
 
ThreadPoolTaskExecutor线程是Spring的线程池,其底层是依据JDK线程池ThreadPoolExecutor来实现的, 其内部常用的变量状态有 :
监控线程池状态
常用状态:
taskCount:线程需要执行的任务个数。
completedTaskCount:线程池在运行过程中已完成的任务数。
largestPoolSize:线程池曾经创建过的最大线程数量。
getPoolSize获取当前线程池的线程数量。
getActiveCount:获取活动的线程的数量
通过继承线程池,重写beforeExecute,afterExecute和terminated方法来在线程执行任务前,线程执行任务结束,和线程终结前获取线程的运行情况,根据具体情况调整线程池的线程数量。

注意: 有些状态在 ThreadPoolTaskExecutor 可能获取不到, 可以先获取 ThreadPoolExecutor , 再获取其状态, 示例代码如下:

ThreadPoolTaskExecutor threadPoolTaskExecutor=(ThreadPoolTaskExecutor)executor;
System.out.println(threadPoolTaskExecutor.getThreadPoolExecutor().getCompletedTaskCount());

2. 在方法上开启异步调用方法

使用的方式非常简单,在需要异步的方法上加@Async注解, 创建相应的多线程异步方法类, 如下:

 
@Slf4j
@Service
public class AsyncService {
 
 // 指定使用beanname为doSomethingExecutor的线程池
 @Async("doSomethingExecutor")
 public String doSomething(String message) {
  log.info("do something, message={}", message);
  try {
   Thread.sleep(1000);
  } catch (InterruptedException e) {
   log.error("do something error: ", e);
  }
  return message;
 }
}

3. 调用该异步方法

这里使用 一个 controller来启动调用, 代码如下:

@RestController
public class AsyncController {

    @Autowired
    private AsyncService asyncService;

    @GetMapping("/open/something")
    public String something() {
        int count = 10;
        for (int i = 0; i < count; i++) {
            asyncService.doSomething("index = " + i);
        }
        return "success";
    }
}

启动工程, 访问:localhost:8080/open/something,输出如下:
在这里插入图片描述

由此可见已经达到异步执行的效果了,并且使用到了刚刚自定义配置的线程池。

4. 获取异步方法返回值

当异步方法有返回值时,如何获取异步方法执行的返回结果呢?这时需要异步调用的方法带有返回值CompletableFuture。

CompletableFuture是对Feature的增强,Feature只能处理简单的异步任务,而CompletableFuture可以将多个异步任务进行复杂的组合。如下:

在AsyncService类中添加异步方法, 返回值类型为 CompletableFuture


@Slf4j
@Service
public class AsyncService {

    @Resource
    @Qualifier("doSomethingExecutor")
    private Executor executor;

    // 指定使用 beanName 为 doSomethingExecutor 的线程池
    @Async("doSomethingExecutor")
    public String doSomething(String message) {
        //
        log.info("{}, message={}", Thread.currentThread().getName(), message);
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            log.error("{} error: ", Thread.currentThread().getName(), e);
        }
        return message;
    }

    @Async("doSomethingExecutor")
    public CompletableFuture<String> doSomethingComp(String message) throws InterruptedException {
        log.info("do something1: {}", message);
        Thread.sleep(1000);
        return CompletableFuture.completedFuture("do something1: " + message);
    }


}

再在 AsyncController 类中开启调用 :

@RestController
public class AsyncController {

    @Autowired
    private AsyncService asyncService;


    @GetMapping("/open/something")
    public String something() {
        int count = 10;
        for (int i = 0; i < count; i++) {
            asyncService.doSomething("index = " + i);
        }
        return "success";
    }




    @SneakyThrows
    @GetMapping("/open/somethingComp")
    public String somethingComp() {
        int count = 10;
        CompletableFuture[] futures = new CompletableFuture[count];
        // 开启待返回值得异步方法
        for (int i = 0; i < count; i++) {
            futures[i] = asyncService.doSomethingComp("index = " + i);
        }
        try {// 等待所有任务都执行完
            CompletableFuture.allOf(futures).join();
        } catch (Exception e) {
            System.out.println("CompletableFuture error");
        }

        System.out.println("Get all return value! ");
        return "success";
    }
}

然后 访问该接口 :http://localhost:8080/open/somethingComp ,输出如下:
在这里插入图片描述
可以看到使用CompletableFuture.join 方法 等待所有异步方法执行完成后才执行下面的语句.

5. 注意事项

@Async注解会在以下几个场景失效,也就是说明明使用了@Async注解,但就没有走多线程。

  • 异步方法使用static关键词修饰;
  • 异步类不是一个Spring容器的bean(一般使用注解@Component和@Service,并且能被Spring扫描到);
  • SpringBoot应用中没有添加@EnableAsync注解;
  • 在同一个类中,一个方法调用另外一个有@Async注解的方法,注解不会生效。原因是@Async注解的方法,是在代理类中执行的。

其他:
异步方法使用注解@Async的返回值只能为void或者Future及其子类,当返回结果为其他类型时,方法还是会异步执行,但是返回值都是null

参考: Spring Boot如何优雅的使用多线程实例详解
CompletableFuture等待所有任务结束
Spring线程池ThreadPoolTaskExecutor学习总结

  • 2
    点赞
  • 0
    评论
  • 5
    收藏
  • 打赏
    打赏
  • 扫一扫,分享海报

参与评论 您还未登录,请先 登录 后发表或查看评论
©️2022 CSDN 皮肤主题:技术黑板 设计师:CSDN官方博客 返回首页

打赏作者

知北行

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值