内容纲要

1.序言

Future和Promise是用于处理异步任务的工具,允许人们执行操作而无需等待每个步骤完成。尽管它们都是为了达到相同的目的,但它们之间却存在关键性的差异。在本教程中,我们将探讨Future和Promise之间的差异,深入研究它们的主要特性、用例和独特功能。

2. 理解Future

Future就像一个容器,等待正在进行的操作的结果。开发人员通常使用Future来检查计算的状态,在准备就绪时获取结果,或者优雅地等待操作完成。Future经常与Executor框架集成,为处理异步任务提供了一种简单而高效的方法。

2.1. 关键特性

现在,让我们来探索Future的一些关键特性:

采用阻塞设计,这可能会导致等待异步计算完成。
与正在进行的计算直接交互受到限制,保持了简单的方法。

2.2. 用例

Future在异步操作的结果预先确定且一旦过程开始就无法更改的场景中表现出色。

考虑从数据库中获取用户的个人信息或从远程服务器下载文件。一旦这些操作开始,它们就有固定的结果,如检索到的数据或下载的文件,并且在过程中无法修改。

2.3. 使用Future

要使用Future,我们可以在java.util.concurrent包中找到它们。下面是一个代码片段,演示了如何使用Future来处理异步任务:

ExecutorService executorService = Executors.newSingleThreadExecutor();

Future<String> futureResult = executorService.submit(() -> {
    Thread.sleep(2000);
    return "Future Result";
});

while (!futureResult.isDone()) {
    System.out.println("Future task is still in progress...");
    Thread.sleep(500);
}

String resultFromFuture = futureResult.get();
System.out.println("Future Result: " + resultFromFuture);

executorService.shutdown();

那么,让我们看看运行这段代码后得到的输出:

Future task is still in progress...
Future task is still in progress...
Future task is still in progress...
Future task is still in progress...
Future Result: Future Result

在代码中,futureResult.get() 方法是一个阻塞调用。这意味着当程序执行到这行代码时,它将等待提交给ExecutorService的异步任务完成后再继续执行。

3. 理解Promise

相比之下,Promise的概念并非Java原生的,而是其他编程语言中一种通用的抽象概念。Promise充当一个代理,用于表示在Promise创建时可能未知的值。与Future不同,Promise通常提供更交互式的方法,允许开发人员即使在启动异步计算后也能影响它。

3.1. 关键特性

现在,让我们来探索Promise的一些关键特性:

封装了一个可变状态,允许在异步操作开始后对其进行修改,从而在处理动态场景时提供灵活性。
采用回调机制,允许开发人员附加在异步操作完成、失败或进行时执行的回调。

3.2. 用例

Promise非常适合需要动态和交互式控制异步操作的场景。此外,Promise提供了在启动后修改正在进行的计算的灵活性。这方面的一个好例子是在金融应用中实时流式传输数据,其中显示内容需要适应实时市场变化。

此外,当处理需要基于中间结果进行条件分支或修改的异步任务时,Promise也很有用。一个可能的用例是当我们需要处理多个异步API调用时,后续操作依赖于前一个操作的结果。

3.3. 使用Promise

Java可能没有像JavaScript中那样严格遵循Promise规范的专用Promise类。但是,我们可以使用java.util.concurrent.CompletableFuture来实现类似的功能。CompletableFuture提供了一种灵活的方式来处理异步任务,与Promise共享一些特性。需要注意的是,它们并不完全相同。

下面我们将探讨如何使用CompletableFuture在Java中实现类似Promise的行为:

ExecutorService executorService = Executors.newSingleThreadExecutor();
CompletableFuture<String> completableFutureResult = CompletableFuture.supplyAsync(() -> {
    try {
        Thread.sleep(2000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    return "CompletableFuture Result";
}, executorService);

completableFutureResult.thenAccept(result -> {
      System.out.println("Promise Result: " + result);
  })
  .exceptionally(throwable -> {
      System.err.println("Error occurred: " + throwable.getMessage());
      return null;
  });

System.out.println("Doing other tasks...");

executorService.shutdown();

当我们运行这段代码时,我们会看到以下输出:

Doing other tasks...
Promise Result: CompletableFuture Result

我们创建了一个名为completableFutureResult的CompletableFuture。使用supplyAsync()方法来启动一个异步计算。提供的lambda函数代表异步任务。

接下来,我们使用thenAccept()和exceptionally()将回调附加到CompletableFuture上。thenAccept()回调处理异步任务的成功完成,类似于Promise的解析,而exceptionally()处理任务期间可能发生的任何异常,类似于Promise的拒绝。

关键差异
4.1. 控制流
一旦Future的值被设置,控制流将继续向下进行,不受后续事件或更改的影响。与此同时,Promise(或CompletableFuture)提供了诸如thenCompose()和whenComplete()等方法,用于根据最终结果或异常进行条件执行。
下面我们将创建一个使用CompletableFuture进行分支控制流的示例:

CompletableFuture<Integer> firstTask = CompletableFuture.supplyAsync(() -> {
      return 1;
  })
  .thenApplyAsync(result -> {
      return result * 2;
  })
  .whenComplete((result, ex) -> {
      if (ex != null) {
          // handle error here
      }
  });

在代码中,我们使用thenApplyAsync()方法来演示异步任务的链式处理。

4.2. 错误处理
Future和Promise都提供了处理错误和异常的机制。Future依赖于计算期间抛出的异常:

ExecutorService executorService = Executors.newSingleThreadExecutor();
Future<String> futureWithError = executorService.submit(() -> {
    throw new RuntimeException("An error occurred");
});

try {
    String result = futureWithError.get();
} catch (InterruptedException | ExecutionException e) {
    e.printStackTrace();
} finally {
    executorService.shutdown();
}

在CompletableFuture中,exceptionally()方法用于处理异步计算期间发生的任何异常。如果发生异常,它将打印错误消息并提供一个回退值:

CompletableFuture<String> promiseWithError = new CompletableFuture<>();
promiseWithError.completeExceptionally(new RuntimeException("An error occurred"));

promiseWithError.exceptionally(throwable -> {
    return "Fallback value";
});

4.3. 读写访问

Future 提供了一个只读视图,允许我们在计算完成后检索结果:

Future<Integer> future = executor.submit(() -> 100);
// Cannot modify future.get() after completion

相比之下,CompletableFuture 不仅允许我们读取结果,还允许我们在异步操作开始后动态地设置值:

ExecutorService executorService = Executors.newSingleThreadExecutor();
CompletableFuture<Integer> totalPromise = CompletableFuture.supplyAsync(() -> {
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    return 100;
}, executorService);

totalPromise.thenAccept(value -> System.out.println("Total $" + value ));
totalPromise.complete(10);

最初,我们设置异步任务以返回100作为结果。然而,我们干预并在任务自然完成之前明确地使用值10来完成任务。这种灵活性突显了CompletableFuture的可写特性,允许我们在异步执行期间动态更新结果。

5.总结

在本文中,我们探讨了FuturePromise之间的区别。尽管它们都是为了处理异步任务而存在的,但它们在功能上有显著的不同。

By liu luli

8年IT行业从业经验,参与、负责过诸多大型项目建设。掌握多门编程语言,对Java、Python编程有较为深刻的理解。现为杭州某公司开发负责人。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注