Spring框架 @Async异步注解组件

一、使用方式

1、启动类⾥⾯使⽤ @EnableAsync 注解开启功能,⾃动扫描
2、定义异步任务类并使⽤ @Component 标记组件被容器扫描 , 异步⽅法加上@Async

@Component
@Slf4j
public class SmsComponent {


    /**
     * 异步方法
     */
    @Async
    public void send() {
       System.out.println("方法逻辑");
    }

}

二、失效情况

1、注解 @Async 的⽅法不是 public ⽅法
2、注解 @Async 的返回值只能为 void 或者 Future
3、注解 @Async ⽅法使⽤ static 修饰也会失效
4、spring ⽆法扫描到异步类,没加注解 @Async 或 @EnableAsync注解
5、调⽤⽅与被调⽅不能在同⼀个类
       Spring 在扫描 bean 的时候会扫描⽅法上是否包含 @Async 注解,动态地⽣成⼀个⼦类(即proxy 代理类),当这个有注解的⽅法被调⽤的时候,实际上是由代理类来调⽤的,代理类在调⽤时增加异步作⽤如果这个有注解的⽅法是被同⼀个类中的其他⽅法调⽤的,那么该⽅法的调⽤并没有通过代理类,⽽是直接通过原来的那个 bean ,所以就失效了所以调⽤⽅与被调⽅不能在同⼀个类,主要是使⽤了动态代理,同⼀个类的时候直接调⽤,不是通过⽣成的动态代理类调⽤⼀般将要异步执⾏的⽅法单独抽取成⼀个类类中需要使⽤ @Autowired @Resource 等注解⾃动注⼊,不能⾃⼰⼿动new 对象
6、在 Async ⽅法上标注 @Transactional 是没⽤的,但在 Async ⽅法调⽤的⽅法上标注@Transactional 是有效的

三、注意事项

@Async 注解没指定线程池的话,即未设置TaskExecutor时默认使⽤Spring 创建ThreadPoolTaskExecutor 默认 8 个核⼼线程数占⽤满了之后 , 新的调⽤就会进⼊队列 , ⼤值是Integer.MAX_VALUE,极容易出现 OOM ,或者消息丢失。
  附加: ThreadPoolTaskExecutor ThreadPoolExecutor区别?
   
             1、ThreadPoolExecutor ,这个类是 JDK 中的线程池类,继承⾃ Executor,⾥⾯有⼀个                         execute() ⽅法,⽤来执⾏线程,线程 池主要提供⼀个线程队列,队列中保存着所有等                    待状态的线 程,避免了创建与销毁的额外开销。
             2、ThreadPoolTaskExecutor ,是 spring 包下的,是 Spring 为我 们提供的线程池类 Spring                    异步线程池的接⼝类是 TaskExecutor ,本质还是 java.util.concurrent.Executor
源代码位置:
TaskExecutionProperties
	public static class Pool {

		/**
		 * Queue capacity. An unbounded capacity does not increase the pool and therefore
		 * ignores the "max-size" property.
		 */
		private int queueCapacity = Integer.MAX_VALUE;

		/**
		 * Core number of threads.
		 */
		private int coreSize = 8;

		/**
		 * Maximum allowed number of threads. If tasks are filling up the queue, the pool
		 * can expand up to that size to accommodate the load. Ignored if the queue is
		 * unbounded.
		 */
		private int maxSize = Integer.MAX_VALUE;

		/**
		 * Whether core threads are allowed to time out. This enables dynamic growing and
		 * shrinking of the pool.
		 */
		private boolean allowCoreThreadTimeout = true;

TaskExecutionAutoConfiguration

四、解决方案

spring 会先搜索 TaskExecutor 类型的 bean 或者名字为 taskExecutor的 Executor 类型的 bean,
所以我们最好来⾃定义⼀个线程池,加⼊ Spring IOC 容器⾥⾯,即可覆盖
@Configuration
@EnableAsync
public class ThreadPoolTaskConfig {


    @Bean("threadPoolTaskExecutor")
    public ThreadPoolTaskExecutor threadPoolTaskExecutor(){
         ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();

        //线程池创建的核心线程数,线程池维护线程的最少数量,即使没有任务需要执行,也会一直存活
        //如果设置allowCoreThreadTimeout=true(默认false)时,核心线程会超时关闭
        executor.setCorePoolSize(4);
        //executor.setAllowCoreThreadTimeOut();

        //阻塞队列 当核心线程数达到最大时,新任务会放在队列中排队等待执行
        executor.setQueueCapacity(10000);

        //最大线程池数量,当线程数>=corePoolSize,且任务队列已满时。线程池会创建新线程来处理任务
        //任务队列已满时, 且当线程数=maxPoolSize,,线程池会拒绝处理任务而抛出异常
        executor.setMaxPoolSize(8);

        //当线程空闲时间达到keepAliveTime时,线程会退出,直到线程数量=corePoolSize
        //允许线程空闲时间30秒,当maxPoolSize的线程在空闲时间到达的时候销毁
        //如果allowCoreThreadTimeout=true,则会直到线程数量=0
        executor.setKeepAliveSeconds(30);

        //spring 提供的 ThreadPoolTaskExecutor 线程池,是有setThreadNamePrefix() 方法的。
        //jdk 提供的ThreadPoolExecutor 线程池是没有 setThreadNamePrefix() 方法的
        executor.setThreadNamePrefix("xcw-");

        // rejection-policy:当pool已经达到max size的时候,如何处理新任务
        // CallerRunsPolicy():交由调用方线程运行,比如 main 线程;如果添加到线程池失败,那么主线程会自己去执行该任务,不会等待线程池中的线程去执行
        // AbortPolicy():该策略是线程池的默认策略,如果线程池队列满了丢掉这个任务并且抛出RejectedExecutionException异常。
        // DiscardPolicy():如果线程池队列满了,会直接丢掉这个任务并且不会有任何异常
        // DiscardOldestPolicy():丢弃队列中最老的任务,队列满了,会将最早进入队列的任务删掉腾出空间,再尝试加入队列

        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());

       executor.initialize();
        return executor;
    }


}
// 使⽤实战, 启动类可以不加 @EnableAsync ,改上⾯加
@Async("threadPoolTaskExecutor")

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
好的,下面是一个简单的示例: 假设我们有一个 UserService,其中有一个方法 sendEmail,它需要异步地发送电子邮件。现在我们想要将当前用户的信息透传给异步任务中使用的线程。 首先,我们需要在异步方法上添加 @Async 注解,并在配置类中启用异步支持: ```java @Configuration @EnableAsync public class AppConfig implements AsyncConfigurer { @Override public Executor getAsyncExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(10); executor.setMaxPoolSize(100); executor.setQueueCapacity(10); executor.initialize(); return executor; } @Override public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { return new CustomAsyncExceptionHandler(); } } ``` 在上面的示例中,我们创建了一个 ThreadPoolTaskExecutor,它将用于执行异步任务。我们还实现了 AsyncConfigurer 接口,并覆盖了 getAsyncExecutor 和 getAsyncUncaughtExceptionHandler 方法,以提供自定义的 Executor 和异常处理程序。 现在我们需要将当前用户信息存储在一个 ThreadLocal 对象中。这可以通过一个拦截器来实现: ```java public class UserContextInterceptor extends HandlerInterceptorAdapter { private final ThreadLocal<String> userThreadLocal = new ThreadLocal<>(); @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String currentUser = request.getHeader("X-User"); userThreadLocal.set(currentUser); return true; } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { userThreadLocal.remove(); } public String getCurrentUser() { return userThreadLocal.get(); } } ``` 在上面的示例中,我们创建了一个 UserContextInterceptor,它将在每个请求的开始和结束时执行。在 preHandle 方法中,我们从请求头中获取当前用户信息,并将其存储在一个 ThreadLocal 对象中。在 afterCompletion 方法中,我们将删除该信息,以避免内存泄漏。 现在,我们可以在 UserService 的 sendEmail 方法中使用 UserContextInterceptor 中存储的当前用户信息: ```java @Service public class UserService { @Autowired private JavaMailSender mailSender; @Autowired private UserContextInterceptor userContextInterceptor; @Async public void sendEmail(String to, String subject, String text) { String currentUser = userContextInterceptor.getCurrentUser(); // 使用当前用户信息发送电子邮件 // ... } } ``` 在上面的示例中,我们使用 @Autowired 注解将 UserContextInterceptor 注入到 UserService 中。在 sendEmail 方法中,我们从 UserContextInterceptor 中获取当前用户信息,并在发送电子邮件时使用它。 通过这种方式,我们可以将当前用户信息透传给异步任务中使用的线程。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值