SpringBoot异步线程@Async的使用注意

SpringBoot异步线程@Async的使用注意

当业务需要异步处理的时候(例如异步保存操作日志),我们不能简单的通过new Thread的方式来使用,这样子性能低,重复的创建Thread和回收Thread非常的占用资源,所以我们使用Java的线程池机制,来做到线程的回收利用,线程池的介绍详见我的另一篇文章:java多线程线程池原理剖析

@Async的使用方式如下

一、创建线程池交给Spring管理

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * @author zhangxing
 * @date 2021/5/11
 */
@Configuration
// 使用该注解,打开SpringBoot对异步的支持
@EnableAsync
public class AsyncConfig {

    private static final int CORE_POOL_SIZE = 8;
    private static final int MAX_POOL_SIZE = 16;
    private static final int QUEUE_CAPACITY = 200;
    private static final int KEEP_ALIVE_SECONDS = 60;
    private static final String THREAD_NAME_PREFIX = "LogThreadPool-";

    @Bean("logThreadPoolTaskExecutor")
    public Executor logThreadPoolTaskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 核心线程数量
        executor.setCorePoolSize(CORE_POOL_SIZE);
        // 最大线程数量
        executor.setMaxPoolSize(MAX_POOL_SIZE);
        // 队列中最大任务数
        executor.setQueueCapacity(QUEUE_CAPACITY);
        // 线程名称前缀
        executor.setThreadNamePrefix(THREAD_NAME_PREFIX);
        // 当达到最大线程数时如何处理新任务
        // 此处不使用ThreadPoolExecutor.CallerRunsPolicy()的拒绝策略,如果异步线程池的线程不够用,则会使用将任务提交过来的线程处理,即此处的web容器的主线程,禁止该操作,并发高日志写入慢的时候可能会将主线程全部占用,此时用户无法正常访问系统
        // executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        // 抛弃旧日志的拒绝策略,因为日志可以容忍丢失,所以这里选择丢失最早的没有被处理的任务,如果不能容忍丢失,还需要异步处理,请使用mq中间件或更大的等待队列
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardOldestPolicy());
        // 线程空闲后最大存活时间
        executor.setKeepAliveSeconds(KEEP_ALIVE_SECONDS);
        // 初始化线程池
        executor.initialize();
        return executor;
    }

}

二、异步线程的调用

public class OrderServiceImpl {
  
  @Autowired
  private LogServiceImpl logServiceImpl;
  
  public void createOrder () {
    ....
      // 异步保存日志
      logServiceImpl.saveLog();
  }
}

public class LogServiceImpl {
  
  // 此处必须指明需要使用的线程池的beanName
  @Async("logThreadPoolTaskExecutor")
  public void saveLog() {
    // 保存日志
  }
  
}

三、注意点

  1. 如果@Async中没有指定线程池,则使用SpringBoot默认生成的线程池,通过打印线程信息,可以通过日志看到线程池的类型为SimpleAsyncTaskExecutor,该线程池本质上每来一个新任务都开启新线程,并且没有线程上线,不能起到线程复用的效果,不推荐使用
  2. 拒绝策略如果使用CallerRunsPolicy,那么该异步线程池中的线程不够用时,会将任务还给创建任务的线程来处理,一般正常情况会将任务还给web容器的主线程处理,会影响用户的使用性能,不建议使用
  3. @Async本质上使用线程池,所以异步逻辑中不能使用安全框架中的上下文获取当前用户信息,因为消息做的是异步处理,所以当需要使用当前用户等信息时,应当将这些信息通过参数的形式传入
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值