在Java的异步编程中,CompletableFuture是处理异步任务的核心类,当我们需要同时提交多个异步任务,并且要等待所有任务都执行完成后再进行后续操作时,需要选择正确的等待方式,否则可能出现线程阻塞、异常无法捕获或者程序逻辑不符合预期的问题。

使用allOf结合join等待所有任务
CompletableFuture.allOf是专门用来等待多个CompletableFuture任务全部完成的方法,它接收一个CompletableFuture数组作为参数,返回一个新的CompletableFuture<Void>,当所有传入的任务都完成时,这个新的CompletableFuture才会完成。
如果需要获取所有任务的执行结果,或者需要阻塞当前线程等待所有任务完成,可以结合join方法使用,join方法会阻塞当前线程直到任务完成,和get方法不同的是,它不会抛出受检异常,更适合在lambda表达式中使用。
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CompletableFutureWaitDemo {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(3);
// 创建三个异步任务
CompletableFuture<String> task1 = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "任务1完成";
}, executor);
CompletableFuture<String> task2 = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(1500);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "任务2完成";
}, executor);
CompletableFuture<String> task3 = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(800);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "任务3完成";
}, executor);
// 等待所有任务完成
CompletableFuture<Void> allTasks = CompletableFuture.allOf(task1, task2, task3);
// 阻塞等待,获取各个任务的结果
allTasks.join();
try {
String result1 = task1.get();
String result2 = task2.get();
String result3 = task3.get();
System.out.println("所有任务结果:" + result1 + "," + result2 + "," + result3);
} catch (Exception e) {
e.printStackTrace();
}
executor.shutdown();
}
}
使用allOf结合get等待所有任务
get方法和join方法类似,也可以阻塞当前线程等待任务完成,但是get方法会抛出InterruptedException和ExecutionException两个受检异常,使用时需要进行异常捕获或者声明抛出。
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CompletableFutureGetDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executor = Executors.newFixedThreadPool(2);
CompletableFuture<Integer> taskA = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
return 100;
}, executor);
CompletableFuture<Integer> taskB = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(700);
} catch (InterruptedException e) {
e.printStackTrace();
}
return 200;
}, executor);
CompletableFuture<Void> allTaskFuture = CompletableFuture.allOf(taskA, taskB);
// 使用get方法等待,需要捕获异常
allTaskFuture.get();
System.out.println("任务A结果:" + taskA.get());
System.out.println("任务B结果:" + taskB.get());
executor.shutdown();
}
}
等待时的异常处理
当多个异步任务中有任务执行抛出异常时,allOf返回的CompletableFuture会在所有任务完成后,将异常传递出来。如果使用join方法,会抛出CompletionException,如果使用get方法,会抛出ExecutionException,异常原因可以通过getCause获取。
如果希望某个任务异常时不影响其他任务的等待,可以在单个任务中使用exceptionally方法处理异常,返回默认值,这样allOf等待时不会因为单个任务异常而中断。
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CompletableFutureExceptionDemo {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(2);
// 正常任务
CompletableFuture<String> normalTask = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "正常任务结果";
}, executor);
// 会抛出异常的任务,使用exceptionally处理
CompletableFuture<String> errorTask = CompletableFuture.supplyAsync(() -> {
throw new RuntimeException("任务执行出错");
}, executor).exceptionally(ex -> {
System.out.println("捕获到任务异常:" + ex.getMessage());
return "默认结果";
});
CompletableFuture<Void> allTasks = CompletableFuture.allOf(normalTask, errorTask);
allTasks.join();
System.out.println("正常任务结果:" + normalTask.join());
System.out.println("异常任务结果:" + errorTask.join());
executor.shutdown();
}
}
不同等待方式的对比
我们可以通过下表对比几种常见等待方式的特点:
| 等待方式 | 是否阻塞线程 | 异常处理 | 适用场景 |
|---|---|---|---|
| allOf + join | 是 | 抛出CompletionException,无需声明受检异常 | 需要阻塞等待,且不想处理受检异常的场景 |
| allOf + get | 是 | 抛出InterruptedException和ExecutionException,需要捕获或声明 | 需要明确处理受检异常的场景 |
| allOf + thenRun | 否 | 可以在回调中处理异常 | 不需要阻塞当前线程,希望任务完成后自动执行后续逻辑的场景 |
注意事项
- 不要直接循环调用单个CompletableFuture的
join或者get方法等待所有任务,这样无法保证等待的效率,而且如果前面的任务执行时间长,会浪费等待时间。 - 如果不需要获取任务的返回结果,只需要等待所有任务完成,直接使用
allOf返回的CompletableFuture即可,不需要再单独获取每个任务的结果。 - 使用自定义线程池时,等待完成后记得关闭线程池,避免资源泄漏。
- 如果任务数量不固定,可以将所有CompletableFuture放到集合中,再转换为数组传入
allOf方法。
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class DynamicTaskDemo {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(5);
List<CompletableFuture<String>> taskList = new ArrayList<>();
// 动态添加任务
for (int i = 0; i < 5; i++) {
int index = i;
CompletableFuture<String> task = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(200 * (index + 1));
} catch (InterruptedException e) {
e.printStackTrace();
}
return "任务" + index + "完成";
}, executor);
taskList.add(task);
}
// 转换为数组传入allOf
CompletableFuture<Void> allTasks = CompletableFuture.allOf(taskList.toArray(new CompletableFuture[0]));
allTasks.join();
// 遍历获取结果
for (CompletableFuture<String> task : taskList) {
System.out.println(task.join());
}
executor.shutdown();
}
}
CompletableFuture异步任务allOfjoinget修改时间:2026-07-11 22:00:37