SpringBoot整合异步任务

SpringBoot整合异步任务

一、异步任务核心概念

在传统同步编程中,方法调用会阻塞主线程,直到方法执行完成才能继续后续操作。而异步任务通过独立线程执行耗时操作,主线程无需等待,可直接返回结果或执行其他逻辑,显著提升系统吞吐量和接口响应速度。

核心应用场景

  • 耗时操作:如文件上传、数据导出、第三方接口调用(短信 / 支付回调)。
  • 非实时依赖:如用户注册后异步发送验证邮件,无需等待邮件发送完成即可返回注册成功。
  • 批量处理:如异步统计数据、批量推送消息,避免阻塞主线程。

SpringBoot 异步任务核心组件

  • @Async:标注方法为异步方法,Spring 会自动分配独立线程执行。
  • @EnableAsync:在启动类添加,开启 Spring 异步任务支持。
  • Future:用于接收异步方法的返回值,支持非阻塞获取结果。

二、环境准备

  • JDK
  • SpringBoot 2.7.x(稳定版,兼容性好)
  • 开发工具:IDEA

三、完整实现步骤

image-20251029173758713

1. 项目初始化与依赖配置

(1)创建 SpringBoot 项目

通过Maven创建名SpringBootAsync-Demo为的项目,Group 为 com.yqd

(2)核心依赖(pom.xml)

SpringBoot 已内置异步任务支持,无需额外依赖,核心依赖如下:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <!-- SpringBoot父依赖 -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.4</version>
        <relativePath/>
    </parent>
    <groupId>com.yqd</groupId>
    <artifactId>SpringBootAsync-Demo</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>SpringBootAsync-Demo</name>
    <url>http://maven.apache.org</url>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <!-- Spring Web:提供 HTTP 测试接口 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- Lombok:简化实体类和日志代码 -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <!-- 测试依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

2. 开启异步任务支持

在项目启动类上添加 @EnableAsync 注解,开启 Spring 异步任务功能:

package com.yqd;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;

/**
 * 异步任务项目启动类
 * @EnableAsync:开启 Spring 异步任务支持
 */
@SpringBootApplication
@EnableAsync
public class SpringBootAsyncDemo {
    public static void main(String[] args) {
        SpringApplication.run(SpringBootAsyncDemo.class, args);
    }
}

3. 实现异步任务(无返回值 + 有返回值)

Spring 异步任务支持两种场景:无返回值(如发送短信)和 有返回值(如统计数据),下面分别实现。

(1)异步任务服务类(核心逻辑)

创建 AsyncTaskService 类,通过 @Async 标注异步方法:

package com.yqd.service;

import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.Random;
import java.util.concurrent.Future;

@Service
public class AsyncTaskService {
    @Async
    public void sendSMS() throws InterruptedException {
        System.out.println(LocalDateTime.now().withNano(0) +" 开始调用短信验证码业务方法");
        Long startTime = System.currentTimeMillis();
        Thread.sleep(5000);
        Long endTime = System.currentTimeMillis();
        System.out.println(LocalDateTime.now().withNano(0) +" 短信验证码业务方法执行完毕");
        System.out.println("执行短信业务总耗时:" + (endTime - startTime)+"毫秒");
    }
    @Async
    public Future<Integer> salesStatistics(String area) throws InterruptedException {
        System.out.println(LocalDateTime.now().withNano(0) +" 开始统计【"+area+"】的销售额");
        Long startTime = System.currentTimeMillis();
        int  time=new Random().nextInt(5)+1;
        Thread.sleep(time*1000);
        Long endTime = System.currentTimeMillis();
        System.out.println(LocalDateTime.now().withNano(0) +area+" 地区销售额统计完毕");
        int m=time+1000;
        System.out.println(area+"销售:"+m+"元,统计总耗时:" + (endTime - startTime)+"毫秒");
        return new AsyncResult<Integer>(m);
    }
}

4. 编写测试接口(HTTP 触发)

创建控制器 AsyncTaskController,提供接口测试异步任务:

package com.yqd.controller;

import com.yqd.service.AsyncTaskService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.time.LocalDateTime;
import java.util.concurrent.Future;

@RestController
public class AsyncTaskController {

    @Autowired
    private AsyncTaskService taskService;

    @RequestMapping("/sendSMS")
    public String sendSMS() throws InterruptedException {
        taskService.sendSMS();
        return LocalDateTime.now().withNano(0) + " 成功调用短信验证码服务";
    }

    @RequestMapping("/statistics")
    public String salesStatistics() throws Exception {
        Long st = System.currentTimeMillis();
        //统计北京地区的销售金额
        Future<Integer> f1 = taskService.salesStatistics("北京");
        //统计上海地区的销售金额
        Future<Integer> f2 = taskService.salesStatistics("上海");
        //获取统计结果,并累加
        Integer result = f1.get()+f2.get();
        Long et = System.currentTimeMillis();
        System.out.println("统计总计:"+result+"元,控制层调用总耗时:"+(et-st)+"毫秒");
        return LocalDateTime.now().withNano(0) + " 成功执行销售金额统计";
    }
}

5. 自定义异步线程池(可选,优化性能)

Spring 默认使用 SimpleAsyncTaskExecutor 作为异步线程池,但该线程池无上限,高并发场景可能导致线程过多。建议自定义线程池,控制线程数量和队列大小:

package com.yqd.async.config;

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

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

/**
 * 自定义异步线程池配置
 */
@Configuration
public class AsyncThreadPoolConfig {

    /**
     * 核心线程数:线程池维护的最小线程数(默认=CPU核心数)
     */
    private static final int CORE_POOL_SIZE = 4;

    /**
     * 最大线程数:线程池可创建的最大线程数
     */
    private static final int MAX_POOL_SIZE = 8;

    /**
     * 队列容量:任务等待队列大小
     */
    private static final int QUEUE_CAPACITY = 100;

    /**
     * 空闲线程存活时间:核心线程外的线程空闲多久后销毁(秒)
     */
    private static final int KEEP_ALIVE_SECONDS = 60;

    /**
     * 线程池名称前缀:便于日志排查
     */
    private static final String THREAD_NAME_PREFIX = "Async-Task-";

    @Bean("asyncTaskExecutor") // 自定义线程池 Bean 名称
    public Executor asyncTaskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(CORE_POOL_SIZE);
        executor.setMaxPoolSize(MAX_POOL_SIZE);
        executor.setQueueCapacity(QUEUE_CAPACITY);
        executor.setKeepAliveSeconds(KEEP_ALIVE_SECONDS);
        executor.setThreadNamePrefix(THREAD_NAME_PREFIX);

        // 拒绝策略:当线程池和队列都满时,如何处理新任务
        // CallerRunsPolicy:由调用者(主线程)执行,避免任务丢失
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());

        // 初始化线程池
        executor.initialize();
        return executor;
    }
}

关联自定义线程池

@Async 注解中指定线程池 Bean 名称,使异步任务使用自定义线程池:

// 修改 AsyncTaskService 中的异步方法,指定线程池
@Async("asyncTaskExecutor") // 关联自定义线程池
public void asyncSendSms(String phone) {
    // 原有逻辑不变
}

@Async("asyncTaskExecutor")
public Future<Long> asyncStatisticsSales(String area, int delay) {
    // 原有逻辑不变
}

四、测试验证(详细实操 + 日志示例)

测试前确保项目已启动,端口默认 8080,通过浏览器、Postman 或 curl 工具发起请求,结合控制台日志验证异步效果。

1.测试无返回值异步任务(sendSMS 接口)

  • 访问地址http://localhost:8080/sendSMS

  • 预期结果

    a.接口立即响应(耗时≤10ms),返回内容类似:2025-10-29T17:03:05 成功调用短信验证码服务(主线程未阻塞,直接返回)。

    b.控制台日志输出顺序(体现异步执行)

    2025-10-29T17:25:07 开始调用短信验证码业务方法
    2025-10-29T17:25:12 短信验证码业务方法执行完毕
    执行短信业务总耗时:5011毫秒
    
  • 核心验证点:接口响应时间远小于异步任务执行时间(5 秒),证明主线程未等待。

2.测试有返回值异步任务(statistics 接口)

  • 访问地址http://localhost:8080/statistics

  • 预期结果

    a.接口响应时间≈最长异步任务耗时(因两个任务并行执行,总耗时≈5 秒,而非 1+5=6 秒),返回内容类似:2025-10-29T17:05:20 成功执行销售金额统计。

    b.控制台日志输出(体现并行执行):

    2025-10-29T17:25:45 开始统计【上海】的销售额
    2025-10-29T17:25:45 开始统计【北京】的销售额
    2025-10-29T17:25:46上海 地区销售额统计完毕
    上海销售:1001元,统计总耗时:1005毫秒
    2025-10-29T17:25:48北京 地区销售额统计完毕
    北京销售:1003元,统计总耗时:3005毫秒
    统计总计:2004元,控制层调用总耗时:3006毫秒
    
  • 核心验证点:两个异步任务并行执行,总耗时接近最长单个任务耗时,体现异步并行优化效果。

3.验证自定义线程池(可选)

  • 验证步骤

    a.确保 AsyncTaskService 中异步方法已指定线程池:@Async("asyncTaskExecutor")。

    b.访问任意异步接口(如 /sendSMS),观察控制台日志的线程名。

  • 预期结果:日志中线程名以 Async-Task- 为前缀

  • 结论:线程名符合自定义配置,说明异步任务已使用自定义线程池(而非 Spring 默认线程池)。

六、总结(核心流程 + 最佳实践)

  1. 核心整合流程(三步必做)

    a.开启支持:启动类添加 @EnableAsync,开启 Spring 异步任务机制。

    b.定义异步方法:在 Spring 管理的 Bean 中,用 @Async 标注方法(无返回值→void,有返回值→Future/CompletableFuture)。

    c.触发执行:通过跨 Bean 注入调用异步方法(如 Controller 注入 Service),避免内部调用导致注解失效。

  2. 最佳实践(提升稳定性 + 性能)

  • 必须自定义线程池:默认线程池无上限,高并发易导致 OOM,按 “CPU 密集型 / IO 密集型” 调整参数。

  • 异常必须处理:异步任务异常默认静默失败,通过 “局部捕获 + 全局处理” 确保可观测性。

  • 避免滥用异步:仅用于 “耗时操作 + 非实时依赖” 场景,简单逻辑同步执行更高效。

  • 合理设置超时:有返回值场景用 Future.get(timeout) 避免主线程无限阻塞。

  • 监控线程池:通过 Actuator 暴露指标,实时关注活跃线程数、队列长度,避免任务堆积。

  1. 扩展方向
  • 异步任务编排:使用 CompletableFuture 实现更复杂的流程(如并行执行→结果聚合、任务依赖)。

  • 任务持久化:结合消息队列(RabbitMQ/Kafka),实现异步任务的重试、幂等性、断点续跑(适用于核心业务)。

  • 分布式异步:微服务场景下,使用 Seata/TCC 实现分布式事务,确保异步任务的一致性。

posted @ 2025-10-29 21:49  碧水云天4  阅读(6)  评论(0)    收藏  举报