一、Bug 场景
在一个基于 Java 的微服务应用中,使用 CompletableFuture 来处理异步任务,以提高系统的并发性能。例如,在处理用户注册流程时,会异步调用多个服务进行数据校验、生成账号等操作。然而,在实际运行过程中,发现当某个异步任务出现异常时,没有得到正确的处理,导致整个注册流程看似正常完成,但实际上部分关键数据没有正确生成,影响了业务的正常进行。
二、代码示例
异步任务类(有缺陷)
java
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class AsyncTaskExample {
public static CompletableFuture<String> performAsyncTask() {
return CompletableFuture.supplyAsync(() -> {
// 模拟可能出现异常的操作
if (Math.random() > 0.5) {
throw new RuntimeException("模拟异步任务异常");
}
return "任务成功完成";
});
}
}
测试代码
java
public class CompletableFutureBugExample {
public static void main(String[] args) {
CompletableFuture<String> future = AsyncTaskExample.performAsyncTask();
try {
String result = future.get();
System.out.println("任务结果: " + result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
三、问题描述
- 预期行为:当异步任务出现异常时,能够及时捕获并处理异常,确保业务流程的正确性和完整性。例如,在用户注册场景中,如果某个异步校验或生成操作失败,应该回滚整个注册流程,并向用户返回错误信息。
- 实际行为 :在上述代码中,虽然
CompletableFuture中的任务可能抛出异常,但在调用future.get()时,异常被ExecutionException包装,并且在main方法中只是简单地打印了堆栈信息。这使得业务层无法对具体的异常进行针对性处理,导致即使异步任务失败,程序也可能继续执行后续逻辑,就好像任务成功完成一样,从而丢失了正确的结果,影响业务功能。此外,异常被 "吞噬" 在ExecutionException中,没有被清晰地传递和处理,增加了调试和维护的难度。
四、解决方案
- 使用
exceptionally方法处理异常 :CompletableFuture提供了exceptionally方法,可在异步任务出现异常时,返回一个默认值或执行一些替代操作。
java
import java.util.concurrent.CompletableFuture;
public class AsyncTaskExample {
public static CompletableFuture<String> performAsyncTask() {
return CompletableFuture.supplyAsync(() -> {
if (Math.random() > 0.5) {
throw new RuntimeException("模拟异步任务异常");
}
return "任务成功完成";
}).exceptionally(ex -> {
System.err.println("捕获到异步任务异常: " + ex.getMessage());
return "任务失败,返回默认值";
});
}
}
修改后的测试代码
java
public class CompletableFutureBugExample {
public static void main(String[] args) {
CompletableFuture<String> future = AsyncTaskExample.performAsyncTask();
String result = future.join();
System.out.println("任务结果: " + result);
}
}
- 使用
whenComplete方法 :whenComplete方法可以在任务完成(无论是正常完成还是异常完成)时执行回调函数,在回调函数中可以根据任务状态进行相应处理。
java
import java.util.concurrent.CompletableFuture;
public class AsyncTaskExample {
public static CompletableFuture<String> performAsyncTask() {
return CompletableFuture.supplyAsync(() -> {
if (Math.random() > 0.5) {
throw new RuntimeException("模拟异步任务异常");
}
return "任务成功完成";
}).whenComplete((result, ex) -> {
if (ex != null) {
System.err.println("捕获到异步任务异常: " + ex.getMessage());
}
});
}
}
结合 handle 方法获取结果或处理异常
java
import java.util.concurrent.CompletableFuture;
public class AsyncTaskExample {
public static CompletableFuture<String> performAsyncTask() {
return CompletableFuture.supplyAsync(() -> {
if (Math.random() > 0.5) {
throw new RuntimeException("模拟异步任务异常");
}
return "任务成功完成";
}).handle((result, ex) -> {
if (ex != null) {
System.err.println("捕获到异步任务异常: " + ex.getMessage());
return "任务失败,返回默认值";
}
return result;
});
}
}