SpringBoot中@Async异步多线程使用

一.概念

同步: 同步就是整个处理过程顺序执行,当各个过程都执行完毕之后,返回结果。
异步: 调用者无需等待被调用的方法完全执行完毕,而是继续执行下面的流程。

二.异步多线程概述

在实际项目开发中很多业务场景需要使用异步去完成,比如消息通知,日志记录以及完成大数据的导入导出等常用的功能都可以通过异步去执行,提高效率。

1.@Async的默认使用

如果在方法上直接使用注解@Async,默认使用的线程池是SimpleAsyncTaskExecutor,默认线程池会为每个任务新起一个线程,默认线程数不做限制,不复用线程,也就说只要你的任务耗时长一点,说不定服务器就给你来个OOM,最好的办法就是使用自定义的线程池。自定义线程池,可对系统中线程池更加细粒度的控制,方便调整线程池大小配置,线程执行异常控制和处理。

2.自定义线程池来使用@Async

如果想要@Async默认调用自定义的线程池,关于@Async的默认调用规则,会优先查询实现了AsyncConfigurer这个接口的类或者继承AsyncConfigurerSupport的类,所以可以按照如下代码中自定义线程池。新建一个线程池配置类,@EnableAsync在配置类上加,不用在启动类上加也行,用来开启对异步任务的支持。可以配置不同的线程池,如果配置了多个线程池,可以用@Async(“name”),那么表示线程池的@Bean的name,来指定用哪个线程池处理,假如只配置了一个线程池,直接用@Async就会用自定义的线程池执行。假如配置了多个线程池,用@Async没指定用哪个线程池,会用默认的SimpleAsyncTaskExecutor来处理。

@Configuration
//表示开启对异步任务的支持
@EnableAsync
//关于@Async的默认调用规则,会优先查询源码中实现AsyncConfigurer这个接口的类
//若想让@Async默认调用自定义线程池,则可以实现该接口
public class ExecutorConfig implements AsyncConfigurer {

    //获取属性文件中对应的值,如果属性文件中没有这个属性,则获取赋予的默认值,如默认核心线程数为4
    @Value("${async.executor.thread.core_pool_size:4}")
    private int corePoolSize;
    @Value("${async.executor.thread.max_pool_size:16}")
    private int maxPoolSize;
    @Value("${async.executor.thread.queue_capacity:9999}")
    private int queueCapacity;
    @Value("${async.executor.thread.name.prefix:async_task_}")
    private String namePrefix;

    @Override
    @Bean
	//自动装配时当出现多个Bean候选者时,被注解为@Primary的Bean将作为首选者,否则将抛出异常
    @Primary
    public Executor getAsyncExecutor() {
        LOG.info("start asyncServiceExecutor");
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        //配置核心线程数
        executor.setCorePoolSize(corePoolSize);
        //配置最大线程数
        executor.setMaxPoolSize(maxPoolSize);
        //配置队列大小
        executor.setQueueCapacity(queueCapacity);
        //配置线程池中的线程的名称前缀
        executor.setThreadNamePrefix(namePrefix);
        // rejection-policy:当pool已经达到max size的时候,如何处理新任务
        // CALLER_RUNS:不在新线程中执行任务,而是有调用者所在的线程来执行
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        //执行初始化
        executor.initialize();
        return executor;
    }

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return (Throwable e, Method method, Object... args) -> {
            LOG.error("异步运行方法出错, method:{}, args:{}, message:{}", method, Arrays.deepToString(args), e.getMessage(), e);
        };
    }
}

三.@Async注解失效原因

1.未开启异步配置,需要在SpringBoot启动类上添加@EnableAsync注解,或者在异步线程池配置类中添加@EnableAsync。
2.异步方法和调用者方法在同一个类中,为什么异步方法与调用者在同一个内中会失效?原因是:spring 在扫描bean的时候会扫描方法上是否包含@Async注解,如果包含,spring会为这个bean动态地生成一个子类(即代理类,proxy),代理类是继承原来那个bean的。此时,当这个有注解的方法被调用的时候,实际上是由代理类来调用的,代理类在调用时增加异步作用。然而,如果这个有注解的方法是被同一个类中的其他方法调用的,那么该方法的调用并没有通过代理类,而是直接通过原来的那个bean,所以就没有增加异步作用,我们看到的现象就是@Async注解无效。将异步方法按照业务统一抽取到对应的bean中,当外部需要使用时将该bean注入,然后调用bean中的异步方法。
3.异步方法使用注解@Async的返回值只能为void或者Future,注解的方法必须是public方法。
4.异步方法使用static修饰

你可能感兴趣的:(java,spring,boot,java)