SpringBoot中的异步调用

一、基础实现

1. 开启异步调用支持
  • 在启动类上添加@EnableAsync注解
  • @EnableAsync可以配置在启动类(程序入口)或者配置类上,这里我配置在了启动类上
@SpringBootApplication
@EnableAsync // 开启异步调用
public class XxxApplication {
    public static void main(String[] args) {
        SpringApplication.run(XxxApplication .class, args);
    }
}
2. 声明异步方法

注意:

  • 在需要异步的方法上添加@Async注解;
  • @Async注解可以配置在类或者方法上,若配置在类上则说明这个类下的所有方法都将支持异步调用
  • 异步方法所属的类应该是spring管理的类;
@Service
public class TestServiceImpl implements ITestService {

    @Override
    @Async // 声明异步方法
    public void testAsynTask(int i) {
        LoggerFactory.getLogger(TestServiceImpl.class).info(">>>>>>>>> " + i);
    }
}
3.调用异步方法
@RestController
@RequestMapping("/test")
public class TestController {
    @Autowired
    private ITestService testService;
    
    @ApiOperation(value = "testAsynTask", notes = "测试springBoot异步调用")
    @GetMapping("/testAsynTask")
    public String testAsynTask(){
        for (int i = 0; i < 10; i++) {
            testService.testAsynTask(i);
        }
        return "success";
    }
}

打印结果:
打印结果

二、进阶

有时候,根据需求需要制定一些多线程的策略(配置);或者需要知道线程何时结束返回值如何获取…

1. 配置
@Configuration // 定义一个配置类
@EnableAsync // 开启异步调用
public class BeanConfig {

    @Bean
    public TaskExecutor taskExecutor() {
        // ThreadPoolTaskExecutor 这个类是sring为我们提供的线程池类
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 设置核心线程数
        executor.setCorePoolSize(3);
        // 设置最大线程数
        executor.setMaxPoolSize(5);
        // 设置队列容量
        executor.setQueueCapacity(10);
        // 设置线程活跃时间(秒)
        executor.setKeepAliveSeconds(60);
        // 设置默认线程名称
        executor.setThreadNamePrefix("thread_by_boo-");
        // 设置拒绝策略
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        // 等待所有任务结束后再关闭线程池
        executor.setWaitForTasksToCompleteOnShutdown(true);
        return executor;
    }
}
2. 接收线程的返回值

有时候我们不止希望异步执行任务,还希望任务执行完成后会有一个返回值,在java中提供了Future泛型接口,用来接收任务执行结果,springboot也提供了此类支持,使用实现了ListenableFuture接口的类如AsyncResult来作为返回值的载体。比如上例中,我们希望返回一个类型为String类型的值,可以将返回值改造为:

@Async
    public Future<String> testAsynTask(int i) {
        log.info(">>>>>>>>> " + i);
        return new AsyncResult<>("我是" + i + "wa");
    }

调用:

for (int i = 0; i < 10; i++) {
    // Future 的 get方法可以获得返回值
    // 阻塞调用  相当于加了个锁,上一个线程不结束 下一个线程进不去
//  testService.testAsynTask(i).get();
    // 限时调用  超过规定的调用时间  会报超时异常
//  testService.testAsynTask(i).get(1, TimeUnit.SECONDS);
    Future<String> res = testService.testAsynTask(i);
}
  • 监听单个线程结束
while(true){
    if (res.isDone()){
        System.out.println("线程结束....");
        break;
    }
}
  • 监听多个线程结束(一)
public String testAsynTask() throws Exception {
    List<Future> futureList = Lists.newArrayList(); // 声明一个线程容器
    for (int i = 0; i < 10; i++) {
        Future<String> res = testService.testAsynTask(i);
        futureList.add(res); // 每得到一个线程就往容器中添加一个线程
    }

    // 监听线程是否全部结束
    while (true){
        if (futureList.size() < 1) { // 当线程容器中没有任何线程的时候 说明线程已经全部结束
            System.out.println("线程已经全部结束...");
            break;
        }
        for (int i = 0; i < futureList.size(); i++) {
            Future<String> future = futureList.get(i);
            if (future.isDone()){ // 线程结束 则将其从线程容器中移除
                System.out.println(future.get() + " >> 线程结束");
                futureList.remove(i);
                break;
            }
        }
    }
    return "success";
}

打印结果:
打印结果

监听多个线程结束(二)

使用CountDownLatch工具类
countDownLatch是一个计数器,线程完成一个记录一个,计数器递减

大致流程如下

  1. 声明一个计数器,数量为线程的数量;
  2. 线程中对计数器进行递减处理,每个线程结束时计数器都减1;
  3. 调用CountDownLatch的await()方法,调用await()方法的线程会被挂起,它会等待直到count值为0才继续执行;
  4. 程序继续向下运行…

由此,上面的代码可以改造为:

  • 调用方:
List<Future<String>> futureList = Lists.newArrayList(); // 声明一个线程容器
 // 声明一个计数器 数量为线程的总数量
 CountDownLatch countDownLatch = new CountDownLatch(10);
 for (int i = 0; i < 10; i++) {
     Future<String> res = testService.testAsynTask(i, countDownLatch);
     futureList.add(res); // 每得到一个线程就往容器中添加一个线程
 }

 // 阻塞  直到线程全部结束
 countDownLatch.await();
 log.info("线程已经全部结束....");
 for (Future<String> future : futureList) {
     log.info(future.get());
 }
  • 被调用方:
public Future<String> testAsynTask(int i,CountDownLatch countDownLatch) {
    try {
        log.info("我是" + i + "wa");
        if (i == 2 || i == 3 || i == 4){
            System.out.println(1 / 0); // 使线程异常
        }
        return new AsyncResult<>(i + "wa正常");
    }catch(Exception e){
        e.getMessage();
    }finally {
        // 在 finally中递减 确保每个线程结束 计数器都能递减
        countDownLatch.countDown();
    }
    return new AsyncResult<>(i + "wa挂了");
}

打印:
打印结果
类似的还有一个CyclicBarrier的计数器,有兴趣可以了解一下

  • CountDownLatchCyclicBarrier区别:
  1. countDownLatch是一个计数器,线程完成一个记录一个,计数器递减,只能只用一次
  2. CyclicBarrier的计数器更像一个阀门,需要所有线程都到达,然后继续执行,计数器递增,提供reset功能,可以多次使用

springBoot配置线程池

  • 配置类
package com.boo.common.config.thread;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * description: ThreadConfig <br>
 * date: 2023/3/9 16:52 <br>
 * author: Boo <br>
 * version: 1.0 <br>
 */
@Configuration
@EnableAsync  // 启用异步任务
public class ThreadConfig {

    /**
     * IO密集型任务  = 一般为2*CPU核心数(常出现于线程中:数据库数据交互、文件上传下载、网络数据传输等等)
     * CPU密集型任务 = 一般为CPU核心数+1(常出现于线程中:复杂算法)
     * 混合型任务  = 视机器配置和复杂度自测而定
     */
    public static final int cpuNum = Runtime.getRuntime().availableProcessors();

    // 声明一个线程池(并指定线程池的名字)
    @Bean("executeTaskExecutor")
    public Executor asyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        //核心线程数 cpuNum:线程池创建时候初始化的线程数
        executor.setCorePoolSize(cpuNum);
        //核心线程数 cpuNum:线程池创建时候初始化的线程数
        executor.setMaxPoolSize(cpuNum * 2);
        //缓冲队列500:用来缓冲执行任务的队列
        executor.setQueueCapacity(500);
        //允许线程的空闲时间60秒:当超过了核心线程出之外的线程在空闲时间到达之后会被销毁
        executor.setKeepAliveSeconds(60);
        // CallerRunsPolicy策略:如果添加到线程池失败,那么主线程会自己去执行该任务,不会等待线程池中的线程去执行。
        //当提交的任务个数大于QueueCapacity,就需要设置该参数,但spring提供的都不太满足业务场景,可以自定义一个,也可以注意不要超过QueueCapacity即可
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        //调度器shutdown被调用时等待当前被调度的任务完成
        executor.setWaitForTasksToCompleteOnShutdown(true);
        //等待时长
        executor.setAwaitTerminationSeconds(60);
        //线程池名的前缀:设置好了之后可以方便我们定位处理任务所在的线程池
        executor.setThreadNamePrefix("BooAsync-");
        executor.initialize();
        return executor;
    }

    @Bean
    public PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
        PropertySourcesPlaceholderConfigurer p = new PropertySourcesPlaceholderConfigurer();
        p.setIgnoreUnresolvablePlaceholders(true);
        p.setOrder(1);
        return p;
    }

}

扩展

小心掉坑里!5分钟了解SpringBoot使用@Async注解8大陷阱
远程调用feign和异步线程存在请求数据丢失问题
Spring Boot 异步线程间数据传递

  • 3
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Spring Boot进行异步请求可以使用Spring的异步支持。以下是一种常见的实现方式: 1. 首先,在Spring Boot应用程序的配置类或主类上添加`@EnableAsync`注解,以启用异步支持。 ```java import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @EnableAsync @SpringBootApplication public class YourApplication { public static void main(String[] args) { SpringApplication.run(YourApplication.class, args); } } ``` 2. 在需要进行异步请求的方法上添加`@Async`注解,表明该方法是一个异步方法。 ```java import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; @Service public class YourService { @Async public void doAsyncOperation() { // 异步执行的逻辑 } } ``` 3. 在需要调用异步方法的地方,通过依赖注入的方式获取到异步方法所在的类的实例,并调用相应的异步方法。 ```java import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class YourController { @Autowired private YourService yourService; @GetMapping("/async") public String asyncRequest() { yourService.doAsyncOperation(); return "Async request submitted"; } } ``` 以上代码示例,`YourService`类的`doAsyncOperation`方法是一个异步方法,在`YourController`类的`asyncRequest`方法调用了该异步方法。在调用异步方法后,控制权会立即返回给调用方,而异步方法会在后台线程执行。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值