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功能,可以多次使用

猜你喜欢

转载自blog.csdn.net/HELLOMRP/article/details/105660409