SpringBoot中异步注解@Async的使用以及注意事项

本文详细介绍了在Spring框架中如何配置和使用异步任务,包括开启异步任务的基本步骤,自定义线程池的方法,以及@Async注解的正确使用方式。同时,文章还列举了常见的错误用法,帮助读者避免异步任务配置中的常见陷阱。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

第一步开启异步:

@Configuration
@EnableAsync
public class SpringAsyncConfig { ... }

默认情况下,@EnableAsync检测Spring的@Async注释和EJB 3.1 javax. EJB .异步;此选项还可用于检测其他用户定义的注释类型。(也可以在SpringBoot的启动类上直接加@EnableAsync注解)

在 Spring 中,用 @Async 注解指定的方法,该方法被调用时会以异步的方式执行。而如果没有在 @Async 注解中指定线程池,就会使用默认的线程池。默认的线程池为 SimpleAsyncTaskExecutor 。

该线程池不会复用线程,每有一个新任务被提交,该线程池就会创建一个新的线程实例用于执行任务。下面为相关的代码:

protected void doExecute(Runnable task) {
    Thread thread = (this.threadFactory != null ? this.threadFactory.newThread(task) : createThread(task));
    thread.start();
}

而如果想要指定线程池,可以通过在 @Async 注解中的 value 参数中指定所要使用的线程池的 Bean Name 。另一种方法是是一个实现了 AsyncConfigurer 接口或是继承其默认适配器类 AsyncConfigurerSupport 的配置类,这样 @Async 注解的方法就会使用指定的自定义的线程池。

使用@Async注解的话采用的是springBoot默认的线程池,不过一般我们会自定义线程池(因为比较灵活),配置方式有:

  1. 使用 xml 文件配置的方式

  2. 使用Java代码结合@Configuration进行配置(推荐使用)

下面显示配置线程的代码实现

package com.deppon.ptos.load.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

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

/**
 * @Description: 异步线程管理
 * @Author:   LYH
 * @CreateDate: 2019/6/27 8:54
 * @Version: 1.0
 * @JDK: 1.8
 */
@Configuration
@EnableAsync
@Slf4j
public class ExecutorConfig {


    @Value("${async.executor.thread.core_pool_size}")
    private int corePoolSize;
    @Value("${async.executor.thread.max_pool_size}")
    private int maxPoolSize;
    @Value("${async.executor.thread.queue_capacity}")
    private int queueCapacity;
    @Value("${async.executor.thread.name.prefix}")
    private String namePrefix;

    @Bean(name = "asyncServiceExecutor")
    public Executor asyncServiceExecutor() {
        log.info("start asyncServiceExecutor");
        ThreadPoolTaskExecutor executor = new VisiableThreadPoolTaskExecutor();
        //配置核心线程数
        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;
    }
}
package com.deppon.ptos.load.config;


import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import org.springframework.util.concurrent.ListenableFuture;

import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * @Description: 打印异步线程的执行情况   使用Callbale Future 来返回线程的信息
 * @Author: 633805  LYH
 * @CreateDate: 2019/6/27 8:59
 * @Version: 1.0
 * @JDK: 1.8
 */
@Component
@Slf4j
public class VisiableThreadPoolTaskExecutor extends ThreadPoolTaskExecutor {


    private void showThreadPoolInfo(String prefix) {
        ThreadPoolExecutor threadPoolExecutor = getThreadPoolExecutor();

        if (null == threadPoolExecutor) {
            return;
        }

        log.info("{}, {},taskCount [{}], completedTaskCount [{}], activeCount [{}], queueSize [{}]",
                this.getThreadNamePrefix(),
                prefix,
                threadPoolExecutor.getTaskCount(),
                threadPoolExecutor.getCompletedTaskCount(),
                threadPoolExecutor.getActiveCount(),
                threadPoolExecutor.getQueue().size());
    }

    @Override
    public void execute(Runnable task) {
        showThreadPoolInfo("1. do execute");
        super.execute(task);
    }

    @Override
    public void execute(Runnable task, long startTimeout) {
        showThreadPoolInfo("2. do execute");
        super.execute(task, startTimeout);
    }

    @Override
    public Future<?> submit(Runnable task) {
        showThreadPoolInfo("1. do submit");
        return super.submit(task);
    }

    @Override
    public <T> Future<T> submit(Callable<T> task) {
        showThreadPoolInfo("2. do submit");
        return super.submit(task);
    }

    @Override
    public ListenableFuture<?> submitListenable(Runnable task) {
        showThreadPoolInfo("1. do submitListenable");
        return super.submitListenable(task);
    }

    @Override
    public <T> ListenableFuture<T> submitListenable(Callable<T> task) {
        showThreadPoolInfo("2. do submitListenable");
        return super.submitListenable(task);
    }
}

使用:

@Async("asyncServiceExecutor")

到这一步,异步就算开启了。

下面主要说一说错误的使用@Async导致异步不成功的情况:

如下方式会使@Async失效

  • 异步方法使用static修饰

  • 异步类没有使用@Component注解(或其他注解)导致spring无法扫描到异步类

  • 异步方法不能与被调用的异步方法在同一个类中

  • 类中需要使用@Autowired或@Resource等注解自动注入,不能自己手动new对象

  • 如果使用SpringBoot框架必须在启动类中增加@EnableAsync注解

如果想更加详细了解请参考:https://juejin.im/post/5d47a80a6fb9a06ad3470f9a

### Spring Boot使用 `@Async` 实现异步方法调用 #### 方法实现示例 为了在 Spring Boot 应用中启用并使用 `@Async` 注解,需完成以下两部分工作: 1. **启用异步功能** 需要在主应用程序类或任意配置类上添加 `@EnableAsync` 注解。这一步是必要的,因为只有启用了该注解,Spring 才会识别带有 `@Async` 的方法。 ```java @EnableAsync @SpringBootApplication public class AsyncExampleApplication { public static void main(String[] args) { SpringApplication.run(AsyncExampleApplication.class, args); } } ``` 2. **定义异步方法** 在目标方法上添加 `@Async` 注解即可将其标记为异步执行的方法。默认情况下,这些方法会在单独的线程中运行[^1]。 下面是一个简单的例子,展示如何定义一个返回值类型的异步方法: ```java import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; @Service public class AsyncTask { @Async public CompletableFuture<String> performTask() throws InterruptedException { Thread.sleep(2000); // 模拟耗时任务 return CompletableFuture.completedFuture("Task Completed"); } } ``` 如果不需要返回任何结果,则可以将返回类型设为 `void` 或者 `CompletableFuture<Void>`: ```java @Async public void performVoidTask() throws InterruptedException { Thread.sleep(2000); // 模拟耗时任务 System.out.println("Void Task Completed"); } ``` #### 自定义线程池配置 虽然 Spring 默认提供了一个基于 `ThreadPoolTaskExecutor` 的简单线程池,但在实际生产环境中通常建议自定义线程池以满足特定需求[^3]。可以通过创建一个 Bean 来覆盖默认行为: ```java import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; @Configuration public class AsyncConfig { @Bean(name = "taskExecutor") public ThreadPoolTaskExecutor taskExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(5); executor.setMaxPoolSize(10); executor.setQueueCapacity(25); executor.initialize(); return executor; } } ``` 此时,所有的 `@Async` 方法都会自动使用这个新定义的线程池来执行任务。 #### 超时处理机制 当某些异步任务可能长时间未响应时,可以引入超时控制逻辑。一种常见的方式是利用 Java 提供的 `Future.get(long timeout, TimeUnit unit)` 方法设置最大等待时间[^2]。例如: ```java @Service public class TimeoutTask { private final AsyncTask asyncTask; public TimeoutTask(AsyncTask asyncTask) { this.asyncTask = asyncTask; } public String handleTimeout() { try { return asyncTask.performTask().get(1, TimeUnit.SECONDS); // 设置最长等待时间为1秒 } catch (Exception e) { return "Operation timed out"; } } } ``` 如果超过指定的时间范围仍未收到结果,则抛出异常,并可通过捕获此异常来进行后续处理。 --- ### 注意事项 - 启用 `@EnableAsync` 是必不可少的前提条件。 - 建议始终显式声明线程池参数而非依赖于框架内置的默认值。 - 对于有状态的对象实例化后不可再用于其他事务上下文中;因此应尽量避免让此类对象参与异步操作流程之中[^4]。 - 当涉及多个模块间的协作时,请确保各组件间共享相同的线程管理策略以免造成混乱或者性能瓶颈等问题发生。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值