Spring Boot 中使用 @Async 实现异步任务及 Future 回调详解

清欢
清欢
发布时间:2026-10-16 | 2人浏览过
原创

在 Spring Boot 应用中,对于相互独立且耗时的操作,同步串行执行会严重影响响应速度。本文演示如何通过 @EnableAsync 和 @Async 将普通方法改为异步执行,并利用 Future 接口处理任务结果的回调与状态等待,从而优化并发性能。

异步调用的核心原理

同步调用要求程序按预定顺序逐步执行,每一步必须等待上一步完成后才能继续;而异步调用允许程序无需等待前置步骤完成即可发起新任务。这种机制能有效缩短程序的总体执行时间。

1. 环境配置与基础准备

要在 Spring Boot 中启用异步支持,需在启动类上添加特定注解,并创建包含异步逻辑的任务组件。

第1步:开启异步支持

入口: Spring Boot 启动类或配置类
动作: 添加 @EnableAsync 注解
目的: 激活 Spring 的异步执行框架。

第2步:定义任务抽象基类

动作: 创建抽象类 AbstractTask,包含三个模拟耗时操作的实例方法。
参数说明: 使用随机数模拟任务执行时间的不确定性(0-10秒)。

public abstract class AbstractTask {
    private static Random random = new Random();

    public void doTaskOne() throws Exception {
        System.out.println("开始做任务一");
        long start = currentTimeMillis();
        sleep(random.nextInt(10000));
        long end = currentTimeMillis();
        System.out.println("完成任务一,耗时:" + (end - start) + "毫秒");
    }

    public void doTaskTwo() throws Exception {
        System.out.println("开始做任务二");
        long start = currentTimeMillis();
        sleep(random.nextInt(10000));
        long end = currentTimeMillis();
        System.out.println("完成任务二,耗时:" + (end - start) + "毫秒");
    }

    public void doTaskThree() throws Exception {
        System.out.println("开始做任务三");
        long start = currentTimeMillis();
        sleep(random.nextInt(10000));
        long end = currentTimeMillis();
        System.out.println("完成任务三,耗时:" + (end - start) + "毫秒");
    }
}

2. 同步调用示例与局限

首先通过同步方式验证任务的执行流程。创建一个继承自 AbstractTask 的组件,并在单元测试中依次调用三个任务方法。

@Component
public class SyncTask extends AbstractTask {
}

@RunWith(SpringRunner.class)
@SpringBootTest
public class TaskTest {
    @Autowired
    private SyncTask task;
    
    @Test
    public void testSyncTasks() throws Exception {
        task.doTaskOne();
        task.doTaskTwo();
        task.doTaskThree();
    }
}

执行测试后,控制台输出显示任务严格按顺序执行,总耗时为各个任务耗时之和:

开始做任务一
完成任务一,耗时:6720毫秒
开始做任务二
完成任务二,耗时:6604毫秒
开始做任务三
完成任务三,耗时:9448毫秒

虽然功能正常,但若各任务间无数据依赖,这种串行模式会导致整体响应时间过长,不符合高并发场景的需求。

3. 使用 @Async 实现异步并发

针对无依赖关系的任务,可通过 @Async 注解将其转为异步方法,实现并发执行。

第3步:改造为异步组件

入口: 新建 AsyncTask 组件
动作: 重写父类方法,并在方法签名前添加 @Async。
注意: 被 @Async 修饰的方法不能是 static 类型,否则异步机制不生效。

@Component
public class AsyncTask extends AbstractTask {
    @Async
    public void doTaskOne() throws Exception {
        super.doTaskOne();
    }
    @Async
    public void doTaskTwo() throws Exception {
        super.doTaskTwo();
    }
    @Async
    public void doTaskThree() throws Exception {
        super.doTaskThree();
    }
}

第4步:测试异步效果

入口: 修改测试用例,注入 AsyncTask
动作: 依次调用三个异步方法。
结果观察: 主程序发出调用指令后立即结束,由于子线程仍在运行而 JVM 进程终止,可能导致部分或全部任务输出丢失、乱序或不完整。

@Autowired
private AsyncTask asyncTask;
@Test
public void testAsyncTasks() throws Exception {
    asyncTask.doTaskOne();
    asyncTask.doTaskTwo();
    asyncTask.doTaskThree();
}

上述现象的原因是:主线程在异步调用后未等待子线程完成便退出了进程。若要统计并发执行的总耗时并获取结果,需引入回调机制。

4. 基于 Future 的异步回调与等待

为了捕获异步执行的结果并确保主程序等待所有任务结束,需将返回类型改为 Future<T>,并通过轮询或阻塞方式获取状态。

第5步:封装带回调的异步方法

入口: 新建 AsyncCallBackTask 组件
动作: 异步方法返回 AsyncResult 对象,其中包含任务执行结果。
参数: Future<String> 用于表示异步任务的引用。

@Component
public class AsyncCallBackTask extends AbstractTask {
    @Async
    public Future doTaskOneCallback() throws Exception {
        super.doTaskOne();
        return new AsyncResult("任务一完成");
    }
    @Async
    public Future doTaskTwoCallback() throws Exception {
        super.doTaskTwo();
        return new AsyncResult("任务二完成");
    }
    @Async
    public Future doTaskThreeCallback() throws Exception {
        super.doTaskThree();
        return new AsyncResult("任务三完成");
    }
}

第6步:实现任务状态轮询

入口: 测试用例
动作: 接收 Future 返回值,通过 isDone() 循环判断任务是否完成,完成后记录总耗时。

@Autowired
private AsyncCallBackTask asyncCallBackTask;
@Test
public void testAsyncCallbackTask() throws Exception {
    long start = currentTimeMillis();
    
    // 发起异步请求并获得Future引用
    Future task1 = asyncCallBackTask.doTaskOneCallback();
    Future task2 = asyncCallBackTask.doTaskTwoCallback();
    Future task3 = asyncCallBackTask.doTaskThreeCallback();
    
    // 轮询等待所有任务完成
    while (!task1.isDone() || !task2.isDone() || !task3.isDone()) {
        Thread.sleep(1000); // 避免CPU空转
    }
    
    long end = currentTimeMillis();
    System.out.println("任务全部完成,总耗时:" + (end - start) + "毫秒");
}
相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表