springboot多线程 springboot多线程注解_springboot多线程

本文主要讲解 @Async 的基本使用及和 AsyncConfigurer 接口的关系,对于线程池的作用、线程池的参数(核心线程、最大线程......)及运行原理,这里不再过多赘述。

先说一些基础知识,后面再看些示例。

1、在方法上使用该 @Async 注解,申明该方法是一个异步任务。

2、在类上使用该 @Async 注解,申明该类中的所有方法都是异步任务。

3、使用此注解的方法的类对象,必须是spring管理下的bean对象。

4、要想使用异步任务,需要在主启动类或者@configure注解类上开启异步配置,即,配置上 @EnableAsync 注解。对于Spring注解 @Async,Spring是以配置文件的形式来开启 @Async,而SpringBoot则是以注解 @EnableAsync的方式开启。

5、异步方法使用注解 @Async 的返回值只能为 void 或者 Future。

6、@Async 失效处理:

  • 异步方法使用static修饰。不要定义为static类型,这样异步调用不会生效。
  • 方法必须是public方法。
  • 标注@Async注解的方法和调用的方法一定不能在同一个类下,也就是说,方法一定要从另一个类中调用,也就是从类的外部调用,类的内部调用是无效的,因为@Transactional和@Async注解的实现都是基于Spring的AOP,而AOP的实现是基于动态代理模式实现的。那么注解失效的原因就很明显了,有可能因为调用方法的是对象本身而不是代理对象,因为没有经过Spring容器。
  • 使用@Async注解的方法的所在类,一定要交给spring容器来管理。用@Component注解(或其他注解)。

7、若没有通过实现 AsyncConfigurer 接口来自定义线程池,则使用的是Spring默认的线程池 SimpleAsyncTaskExecutor。

8、实现 AsyncConfigurer 接口,主要作用:

  • 自定义线程池。
  • 异常处理。

9、实现 AsyncConfigurer 接口并重写getAsyncExecutor()方法,@Async 默认使用的是重写方法中的线程池。

10、@Async("xxx"),可以使用指定线程池xxx。

11、若有两个实现类实现 AsyncConfigurer 接口,则启动报错。所以说还是比较局限。

12、@Async("xxx")指定使用了其他线程池,出现异常,还是使用的实现 AsyncConfigurer 接口的实现类中的异常处理方法(如果重写了)。也是比较局限(当其他业务不想使用此处理方式时)。

13、异步任务的事务问题:@Async注解的方法由于是异步执行的,在其进行数据库的操作,将无法控制事务管理。 解决办法:可以把@Transactional注解放到内部的需要进行事务的方法上。

14、被 @Async 的方法在独立线程调用,不能被 @ControllerAdvice 全局异常处理器捕获。(这个未验证,有兴趣的可以自行验证)。

下面看些示例

示例1:没有实现AsyncConfigurer情况下,使用@Async

/**
 * 注意这个多线程方法的类要加@Component注解,交给spring容器管理
 */
@Slf4j
@Component
public class AsyncMethods {
    
    @Async
    public void test1(){
        try {
            //暂停一会线程
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
            Thread.currentThread().interrupt();
        }
        log.info(Thread.currentThread().getName());
    }
}

直接在启动类中测试

@EnableAsync
@SpringBootApplication
public class ThreadPoolMainApplication {

    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(ThreadPoolMainApplication.class, args);
        AsyncMethods asyncMethods = context.getBean("asyncMethods", AsyncMethods.class);
        for (int i = 0; i < 5; i++) {
            asyncMethods.test1();
        }
    }
}

结果:使用的是Spring默认的线程池 SimpleAsyncTaskExecutor

springboot多线程 springboot多线程注解_springboot多线程_02

示例2:实现AsyncConfigurer情况下,使用@Async

@Slf4j
@Configuration
public class AsyncConfig implements AsyncConfigurer {

    /**
     * 获取线程池
     *
     * @return
     */
    @Override
    public Executor getAsyncExecutor() {
        return new ThreadPoolExecutor(4,
                5,
                5,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(10),
                new NamedThreadFactory("asyn"),
//                new ThreadPoolExecutor.CallerRunsPolicy()
                new ThreadPoolExecutor.AbortPolicy()
        );
    }

    /**
     * 异常处理
     *
     * @return
     */
    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return (throwable, method, objects) -> {
            //可以做一些其他的业务处理,比如:异常记录日志
            log.error("class: {}", method.getDeclaringClass().getName());
            log.error("method:{}", method.getName());
            log.error("type:{}", throwable.getClass().getName());
            log.error("exception:{}", throwable.getMessage());
        };
    }
}

结果:使用的是自定义的线程池。

springboot多线程 springboot多线程注解_Async_03

示例3:实现AsyncConfigurer,没有重写异常处理方法

@Slf4j
@Component
public class AsyncMethods {

    @Async
    public void test1(){
        try {
            //暂停一会线程
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
            Thread.currentThread().interrupt();
        }
        //制造异常
        System.out.println(10/0);
        log.info(Thread.currentThread().getName());
    }

}

结果:

springboot多线程 springboot多线程注解_Async_04

示例4:实现AsyncConfigurer,重写异常处理方法

异常处理的代码 示例2 中已经给出,直接看结果:

springboot多线程 springboot多线程注解_springboot多线程_05

示例5:使用@Async("xxx"),指定使用其他线程池

自定义线程池:

@Slf4j
@Configuration
public class ThreadPoolConfig {

    @Bean
    public ThreadPoolExecutor myThreadPool() {
        return new ThreadPoolExecutor(4,
                5,
                60,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(10),
                new NamedThreadFactory("hard-study"),
                new ThreadPoolExecutor.AbortPolicy()
        );
    }
}

指定线程池:@Async("myThreadPool")

@Async("myThreadPool")
public void test2(){
    try {
        //暂停一会线程
        TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException e) {
        e.printStackTrace();
        Thread.currentThread().interrupt();
    }
    log.info(Thread.currentThread().getName());
}

直接启动类测试,调用test2方法,其余代码不贴了。

springboot多线程 springboot多线程注解_spring boot_06

结果:使用的是指定的线程池。

springboot多线程 springboot多线程注解_springboot多线程_07

示例6:创建2个实现AsyncConfigurer接口的类,启动报错

代码省略,看结果:


springboot多线程 springboot多线程注解_Async_08

 示例7:若异步方法需要返回值,使用Future<String>、new AsyncResult<>()

@Async
public Future<String> test3(){
    log.info(Thread.currentThread().getName());
    return new AsyncResult<>("任务三完成");
}