@Async一个注解搞定异步编程

448 阅读3分钟

前言

之前写了几篇关于异步编程的文章,FutureFutureTaskCompletableFuture,今天我们来说一下另外一个基于注解的异步编程利器@Async,使用它代码会更加的简洁,更加的规范,不过在使用它的时候也会配合Future接口,下面我们会详细的介绍!

@Async解析

@Async可以使用在方法上面,也可以使用在类上面,如果在类上使用,那么整个类的所有方法都是异步的,@Async注解的value是设置线程池,如果不设置,那么就会使用默认的SimpleAsyncTaskExecutor线程池,不过在实际使用中,我们肯定不能使用默认的,应该自定义一个线程池。

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Async {
    String value() default "";
}

@EnableAsync解析

在SpringBoot中如果要使用@Async,那么需要在项目的启动类上加上@EnableAsync注解,加上@EnableAsync注解,在启动SpringBoot项目的时候它会做一些配置,因为@Async本身就是基于AOP来实现,所以在项目启动的时候会去读取注解的信息,然后做相应的配置,会在Spring Bean进行装配的时候配置,@Async就会在Bean的后置处理BeanPostProcessor做一些配置。

实战演练

一、加上@EnableAsync注解

@EnableAsync
@SpringBootApplication
public class SpringAsyncApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringAsyncApplication.class, args);
    }

}

配置线程池

在实际使用中,我们需要配置线程池,下面的线程池参数已经写死在代码里面,但是生产环境上,我们应该将其配置在统一配置中心中,如nacos,这样,我们就能根据实际情况对参数进行调整。

/**
 * 功能说明:Async线程池配置
 * <p>
 * Original @Author: steakliu-刘牌, 2022-06-26  11:37
 * <p>
 * Copyright (C)2020-2022  小四的技术之旅 All rights reserved.
 */
@Configuration
public class ThreadPoolConfiguration {

    @Bean
    public TaskExecutor taskExecutor(){
        /**
         * CPU核数
         */
        int processors = Runtime.getRuntime().availableProcessors();
        ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
        threadPoolTaskExecutor.setCorePoolSize(processors * 2);
        threadPoolTaskExecutor.setMaxPoolSize(processors * 4);
        threadPoolTaskExecutor.setThreadNamePrefix("async-thread-");
        threadPoolTaskExecutor.setKeepAliveSeconds(60);
        return threadPoolTaskExecutor;
    }
}

Service

AsyncService包含了四个异步任务,task1,task2,task3,task4,值得注意的是,使用@Async的方法,其返回值要么为void, 要么为Future,如果为其他类型,那么返回的为空,因为任务是交给线程池,所以需要用Future来记录执行结果,下面使用了CompletableFuture

/**
 * 功能说明:
 * <p>
 * Original @Author: steakliu-刘牌, 2022-06-26  11:17
 * <p>
 * Copyright (C)2020-2022  小四的技术之旅 All rights reserved.
 */
@Service
@Slf4j
public class AsyncService {

    @Async("taskExecutor")
    public void task1() throws InterruptedException {
        Thread.sleep(2000);
        log.info("=====开始执行task1====={}", Thread.currentThread().getName());
    }

    @Async("taskExecutor")
    public CompletableFuture<String> task2() {
        log.info("=====开始执行task2====={}", Thread.currentThread().getName());
        return CompletableFuture.completedFuture("task2");
    }

    @Async("taskExecutor")
    public void task3() {
        log.info("=====开始执行task3====={}", Thread.currentThread().getName());
    }

    @Async("taskExecutor")
    public void task4() {
        log.info("=====开始执行task4====={}", Thread.currentThread().getName());
    }
}

Controller

/**
 * 功能说明:
 * <p>
 * Original @Author: steakliu-刘牌, 2022-06-26  11:25
 * <p>
 * Copyright (C)2020-2022  小四的技术之旅 All rights reserved.
 */
@RestController
@AllArgsConstructor
@Slf4j
public class AsyncController {

    final AsyncService asyncService;

    @GetMapping("/async")
    public String async() throws InterruptedException, ExecutionException {
        asyncService.task1();
        CompletableFuture<String> future = asyncService.task2();
        log.info("===task2 result=== {}",future.get());
        asyncService.task3();
        asyncService.task4();
        return "async";
    }
}

输出

从输出结果看出几个接口是异步执行的,并且task2返回了值,如果不使用Future作为方法的返回类型,那么将会返回null。

使用其他返回类型

public String task2(){
    log.info("=====开始执行task2====={}",Thread.currentThread().getName());
    return "task2";
}

总结

从上面我们可以看出使用@Async还是比较简单的,特别对于SpringBoot,只需要简单的注解就能完事,不过在使用的时候我们要根据实际情况去考虑该 怎么用,比如数据之间的依赖,返回类型等等。

今天的分享就到这里,感谢你的观看,我们下期见!