CompletableFuture的get()和 join()的区别?

290 阅读3分钟

CompletableFuture 提供了 get()join() 方法,它们都用于等待异步计算的结果,但它们在异常处理和行为上有一些关键的区别。

1. get() 方法

get() 方法是 java.util.concurrent.Future 接口的一部分。它用于等待任务完成并返回结果。如果任务正常完成,则返回结果。如果任务执行时抛出异常,则会将异常封装在 ExecutionException 中并抛出。

特点:

  • 阻塞get() 会阻塞当前线程,直到异步任务完成。
  • 异常处理:如果异步任务抛出了异常,get() 会将该异常包装在 ExecutionException 中并抛出。你需要处理 ExecutionExceptionInterruptedException

示例:

import java.util.concurrent.*;

public class GetExample {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> 42);

        // 阻塞等待异步任务的结果
        Integer result = future.get();
        System.out.println("Result: " + result); // 输出: Result: 42

        // 如果抛出异常,可以捕获
        CompletableFuture<Integer> faultyFuture = CompletableFuture.supplyAsync(() -> {
            throw new RuntimeException("Some error");
        });

        try {
            faultyFuture.get(); // 会抛出 ExecutionException
        } catch (ExecutionException e) {
            System.out.println("Exception occurred: " + e.getCause()); // 输出: Some error
        }
    }
}

2. join() 方法

join() 方法是 CompletableFuture 类特有的方法,类似于 get(),也用于等待异步任务完成并返回结果。但是,它与 get() 的区别在于异常处理。

特点:

  • 阻塞join() 也会阻塞当前线程,直到异步任务完成。
  • 异常处理join() 不会抛出 ExecutionException,而是直接将异常包装成 CompletionException。如果任务执行时抛出异常,join() 会将异常封装在 CompletionException 中并抛出。

示例:

import java.util.concurrent.*;

public class JoinExample {
    public static void main(String[] args) {
        CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> 42);

        // 阻塞等待异步任务的结果
        Integer result = future.join();
        System.out.println("Result: " + result); // 输出: Result: 42

        // 如果抛出异常,可以捕获
        CompletableFuture<Integer> faultyFuture = CompletableFuture.supplyAsync(() -> {
            throw new RuntimeException("Some error");
        });

        try {
            faultyFuture.join(); // 会抛出 CompletionException
        } catch (CompletionException e) {
            System.out.println("Exception occurred: " + e.getCause()); // 输出: Some error
        }
    }
}

3. 主要区别总结

特性get() 方法join() 方法
异常包装如果异步任务抛出异常,get() 会抛出 ExecutionException,需要用 getCause() 获取原始异常。如果异步任务抛出异常,join() 会抛出 CompletionException,需要用 getCause() 获取原始异常。
异常类型ExecutionExceptionInterruptedExceptionCompletionException
中断如果线程在等待时被中断,get() 会抛出 InterruptedException如果线程在等待时被中断,join() 会抛出 CompletionException,包含 InterruptedException
推荐使用场景get() 更适合于需要同时处理多个异常类型的场景,尤其是在与 Future 配合使用时。join() 更简洁,适合于不需要关心异常类型的场景,尤其是当你使用 CompletableFuture 时。

4. 选择哪个方法

  • 如果你需要处理任务执行过程中可能抛出的异常并且希望详细了解异常类型,选择 get()。因为它会明确地将异常包装在 ExecutionException 中,允许你访问 getCause() 方法获取底层的异常。
  • 如果你不关心异常类型,或者只是想在任务失败时捕获异常,选择 join()。它将异常包装成 CompletionException,并且避免了 InterruptedExceptionExecutionException 的复杂处理。

总结:

  • get()join() 都会阻塞当前线程,直到异步任务完成。
  • get() 抛出 ExecutionExceptionInterruptedException,而 join() 抛出 CompletionException
  • 一般情况下,join() 更简洁,适用于 CompletableFuture,而 get() 更适合与 Future 配合使用并需要处理多种类型的异常时。