9.4 Future接口

问题

用户希望执行表示异步计算结果、检查计算是否完成、在必要时取消计算、检索计算结果等操作。

方案

使用能实现 java.util.concurrent.Future 接口的 CompletableFuture 类。

讨论

在本书介绍的 Java 8 和 Java 9 新特性中,CompletableFuture 是一种非常有用的类。由于 CompletableFuture 类可以实现 Future 接口,我们有必要对这种接口的用法做一简要回顾。

Java 5 引入的 java.util.concurrent 包让开发人员可以在更高的抽象层次上操作并发,而不仅限于使用简单的 waitnotify 原语。java.util.concurrent 包定义了一种名为 ExecutorService 的接口,其 submit 方法传入 Callable,并返回包装所需对象的 Future

如例 9-15 所示,程序将任务提交给 ExecutorService 并打印字符串,然后在 Future 中检索值。

例 9-15 提交 Callable 并返回 Future

  1. ExecutorService service = Executors.newCachedThreadPool();
  2. Future<String> future = service.submit(new Callable<String>() {
  3. @Override
  4. public String call() throws Exception {
  5. Thread.sleep(100);
  6. return "Hello, World!";
  7. }
  8. });
  9. System.out.println("Processing...");
  10. getIfNotCancelled(future);

例 9-16 显示了 getIfNotCancelled 方法的应用。

例 9-16 在 Future 中检索值

  1. public void getIfNotCancelled(Future<String> future) {
  2. try {
  3. if (!future.isCancelled()) {
  4. System.out.println(future.get());
  5. } else {
  6. System.out.println("Cancelled");
  7. }
  8. } catch (InterruptedException | ExecutionException e) {
  9. e.printStackTrace();
  10. }
  11. }

❶ 检查 Future 的状态

❷ 通过阻塞调用以检索 Future 的值

在本例中,isCancelled 方法的用途不言自明;get 方法用于在 Future 中检索值,该方法属于阻塞调用(blocking call),返回的是其内部的泛型类型;getIfNotCancelled 方法采用 try/catch 代码块处理声明的异常。

输出结果如下:

  1. Processing...
  2. Hello, World!

由于所提交的调用会立即返回 Future,程序将马上打印“Processing…”。之后调用 get 代码块直到 Future 完成,并打印结果。

既然本书重点讨论 Java 8,我们不妨采用 lambda 表达式替换 Callable 接口的匿名内部类实现,如例 9-17 所示。

例 9-17 使用 lambda 表达式并检查 Future 是否完成

  1. future = service.submit(() -> {
  2. Thread.sleep(10);
  3. return "Hello, World!";
  4. });
  5.  
  6. System.out.println("More processing...");
  7. while (!future.isDone()) {
  8. System.out.println("Waiting...");
  9. }
  10.  
  11. getIfNotCancelled(future);

❶ 使用 lambda 表达式替换 Callable

❷ 等待 Future 完成

可以看到,除使用 lambda 表达式之外,程序还在 while 循环中调用 isDone 方法以轮询 Future,直至它完成。

9.4 Future接口 - 图1 在循环中使用 isDone 方法称为忙等待(busy waiting),由于该方法可能产生数百万次调用,通常属于应该避免的操作。12CompletableFuture 类(详见范例 9.5、范例 9.6 与范例 9.7)可以在 Future 完成时提供更好的处理方式。

12某些情况下可能需要忙等待,如广泛用于操作系统内核的自旋锁(spinlock)。不过,自旋锁的持有时间过长会阻止其他线程运行,导致系统性能降低。——译者注

例 9-17 的输出结果如下:

  1. More processing...
  2. Waiting...
  3. Waiting...
  4. Waiting...
  5. // 一直等待
  6. Waiting...
  7. Waiting...
  8. Hello, World!

当某个 Future 完成时,程序显然需要一种更有效的方式来通知开发人员,计划将这个 Future 的结果用于其他操作时更是如此。CompletableFuture 类的作用就在于此。

此外,可以通过 Future 接口定义的 cancel 方法取消操作,如例 9-18 所示。

例 9-18 取消 Future

  1. future = service.submit(() -> {
  2. Thread.sleep(10);
  3. return "Hello, World!";
  4. });
  5.  
  6. future.cancel(true);
  7.  
  8. System.out.println("Even more processing...");
  9.  
  10. getIfNotCancelled(future);

输出结果如下:

  1. Even more processing...
  2. Cancelled

由于 CompletableFuture 类实现了 Future 接口,本范例讨论的所有方法同样适用于 CompletableFuture 类。

另见

有关 CompletableFuture 的讨论请参见范例 9.5、范例 9.6 与范例 9.7。