当前位置: 首页 > 工具软件 > async-profile > 使用案例 >

【@Async的使用及配置】

贾沛
2023-12-01

大家好,今天为大家介绍一下@Async注解的使用以及配置,还有开发中遇到的问题。

在我们日常的开发中,免不了要考虑使用异步、多线程等使用场景,目前使用的最多的就是自定义线程池和Spring自带的@Async异步的注解了。

@Async的简单使用

@Async注解最简单的使用场景就是,异步发邮件等情况了!
首先我们需要在启动类上加启用异步注解@EnableAsync,然后在需要异步的类上加@Async注解就行了。
启动类:

@SpringBootApplication
@EnableDiscoveryClient
@EnableAsync  //开启异步
public class UserApplication {

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

}

邮件发送实现类:

	/**
     * 发送普通邮件
     *
     * @param emailReqVO
     * @return
     */
    @Override
    @Async //将该方法异步
    public BusinessResponse sendEmail(EmailReqVO emailReqVO) {
        try {
            SimpleMailMessage simpleMailMessage = new SimpleMailMessage();
            simpleMailMessage.setFrom(sendUserName); //设置发送邮件账号
            simpleMailMessage.setTo(emailReqVO.getTos().toArray(new String[emailReqVO.getTos().size()])); //设置接收邮件的人,可以多个
            simpleMailMessage.setSubject(emailReqVO.getSubject()); //设置发送邮件的主题
            simpleMailMessage.setText(emailReqVO.getText()); //设置发送邮件的内容
            mailSender.send(simpleMailMessage);
        } catch (MailException e) {
            throw new BusinessException("邮件发送失败!");
        }
        return BusinessResponse.ok(Boolean.TRUE);
    }

以上就是最简单的使用了浪,是不是简单到都不用讲了,确实就是这么简单樂!

@Async自定义线程池

自定义线程池有几种方法,就看友友们选那个了。

第一种:

@SpringBootApplication
public class UserApplication{
 
    public static void main(String[] args) {
        SpringApplication.run(UserApplication.class, args);
    }
 
    @EnableAsync
    @Configuration
    class TaskPoolConfig {
 
        @Bean("threadPoolTaskExecutor")
        public Executor taskExecutor() {
            // ThreadPoolTaskExecutor是对ThreadPoolExecutor进行了封装处理。
        	ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
        	//队列最大长度
        	threadPoolTaskExecutor.setQueueCapacity(1000);
        	//指定了线程池中的线程数量,它的数量决定了添加的任务是开辟新的线程去执行,还是放到workQueue任务队列中去;
        	threadPoolTaskExecutor.setCorePoolSize(10);
        	//指定了线程池中的最大线程数量,这个参数会根据你使用的workQueue任务队列的类型,决定线程池会开辟的最大线程数量;
        	threadPoolTaskExecutor.setMaxPoolSize(20);
        	//当线程池中空闲线程数量超过corePoolSize时,多余的线程会在多长时间内被销毁;
       	 	threadPoolTaskExecutor.setKeepAliveSeconds(60);
        	//配置线程池中的线程的名称前缀
        	threadPoolTaskExecutor.setThreadNamePrefix("thread-service-");
        	// rejection-policy:当pool已经达到max size的时候,如何处理新任务
        	// CALLER_RUNS:不在新线程中执行任务,而是有调用者所在的线程来执行
        	threadPoolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
            return threadPoolTaskExecutor;
        }
    }
 
}
如何使用:
	/**
     * 发送普通邮件
     *
     * @param emailReqVO
     * @return
     */
    @Override
    @Async("threadPoolTaskExecutor") //使用threadPoolTaskExecutor线程池来进行异步操作
    public BusinessResponse sendEmail(EmailReqVO emailReqVO) {
        try {
            SimpleMailMessage simpleMailMessage = new SimpleMailMessage();
            simpleMailMessage.setFrom(sendUserName); //设置发送邮件账号
            simpleMailMessage.setTo(emailReqVO.getTos().toArray(new String[emailReqVO.getTos().size()])); //设置接收邮件的人,可以多个
            simpleMailMessage.setSubject(emailReqVO.getSubject()); //设置发送邮件的主题
            simpleMailMessage.setText(emailReqVO.getText()); //设置发送邮件的内容
            mailSender.send(simpleMailMessage);
        } catch (MailException e) {
            throw new BusinessException("邮件发送失败!");
        }
        return BusinessResponse.ok(Boolean.TRUE);
    }

第二种:

第二种和第一种其实也差不多只是将线程池配置给了Spring的Async了,这种的使用就帮你解决了异步的方法上还要写@Async("threadPoolTaskExecutor")这么多了,用了之后就可以这样写了@Async,是不是很方便呢?

@Configuration
@EnableAsync
public class AsyncTaskConfigurer implements AsyncConfigurer {

    /**
     * 根据cpu的数量动态的配置核心线程数和最大线程数
     */
    private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
    /**
     * 核心线程数 = CPU核心数 + 1
     */
    private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
    /**
     * 线程池最大线程数 = CPU核心数 * 2 + 1
     */
    private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
    /**
     * 非核心线程闲置时超时1s
     */
    private static final int KEEP_ALIVE = 1;
    /**
     * 队列最大长度 200
     */
    private static final int Queue_Capacity = 1000;
    /**
     * 线程的名称前缀
     */
    private static final String Thread_Name_Prefix = "async-service-";


    /**
     * The {@link Executor} instance to be used when processing async
     * method invocations.
     */
    @Override
    public Executor getAsyncExecutor() {
        //ThreadPoolTaskExecutor是spring core包中的,而ThreadPoolExecutor是JDK中的JUC。
        // ThreadPoolTaskExecutor是对ThreadPoolExecutor进行了封装处理。
        ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
        //队列最大长度
        threadPoolTaskExecutor.setQueueCapacity(Queue_Capacity);
        //指定了线程池中的线程数量,它的数量决定了添加的任务是开辟新的线程去执行,还是放到workQueue任务队列中去;
        threadPoolTaskExecutor.setCorePoolSize(CORE_POOL_SIZE);
        //指定了线程池中的最大线程数量,这个参数会根据你使用的workQueue任务队列的类型,决定线程池会开辟的最大线程数量;
        threadPoolTaskExecutor.setMaxPoolSize(MAXIMUM_POOL_SIZE);
        //当线程池中空闲线程数量超过corePoolSize时,多余的线程会在多长时间内被销毁;
        threadPoolTaskExecutor.setKeepAliveSeconds(KEEP_ALIVE);
        //配置线程池中的线程的名称前缀
        threadPoolTaskExecutor.setThreadNamePrefix(Thread_Name_Prefix);
        // rejection-policy:当pool已经达到max size的时候,如何处理新任务
        // CALLER_RUNS:不在新线程中执行任务,而是有调用者所在的线程来执行
        threadPoolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());

        //执行初始化
        threadPoolTaskExecutor.initialize();
        return threadPoolTaskExecutor;
    }

    /**
     * The {@link AsyncUncaughtExceptionHandler} instance to be used
     * when an exception is thrown during an asynchronous method execution
     * with {@code void} return type.
     */
    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return AsyncConfigurer.super.getAsyncUncaughtExceptionHandler();
    }
}

第三种:

第三种就是使用了Spring自己的默认线程池了

默认使用Spring创建ThreadPoolTaskExecutor
默认核心线程数:8
最大线程数:Integet.MAX_VALUE
队列使用LinkedBlockingQueue
容量是:Integet.MAX_VALUE
空闲线程保留时间:60s
线程池拒绝策略:AbortPolicy

不过我们也可以自己设置默认的线程池,在yml文件下添加如下配置:

spring:
  task:
    execution:
      pool:
        max-size: 10
        core-size: 2
        keep-alive: 3s
        queue-capacity: 1000
      thread-name-prefix: name

以上就是@Async线程池的使用啦,看到这可不可以点个收藏呢!

问题以及解决办法:

Spring boot 注解@Async不生效

  1. @SpringBootApplication启动类当中没有添加@EnableAsync注解。
  2. 异步方法使用注解@Async的返回值只能为void或者Future。
  3. 没有走Spring的代理类。因为@Transactional和@Async注解的实现都是基于Spring的AOP,而AOP的实现是基于动态代理模式实现的。那么注解失效的原因就很明显了,有可能因为调用方法的是对象本身而不是代理对象,因为没有经过Spring容器管理。
@Slf4j
@Service
public class OrderServiceTestImpl extends ServiceImpl<OrderDao, OrderDO> implements OrderServiceTest {
 
    public void getOrderList(){
        asyncGetOrderList();
    }
     
    @Async // 不会生效 
    public void asyncGetOrderList(){
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("异步测试");
    }
 
}
解决方法:
  1. 注解的方法必须是public方法。
  2. 方法一定要从另一个类中调用,也就是从类的外部调用,类的内部调用是无效的。
  3. 如果需要从类的内部调用,需要先获取其代理类。
    将方法抽出来就可以啦!
	public void getOrderList(){
       OrderServiceTestImpl bean = SpringUtil.getBean(OrderServiceTestImpl.class);
       bean.asyncGetOrderList();
	}

再线程套线程的情况这样也可以解决哦,或者抽到另外一个实现类去也是可以的呢!
好啦,本章结束。期待下次见面!!!

 类似资料: