Spring Boot 中使用 @Async 实现异步任务及 Future 回调详解
在 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) + "毫秒");
} 免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









