Future 可以拿结果,但“任务完成后再做一件事”通常还要自己组织等待。Java 8 的 CompletableFuture 把结果和后续处理连在了一起。

创建任务与处理结果

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class CompletableDemo {
    public static void main(String[] args) {
        ExecutorService pool = Executors.newFixedThreadPool(2);
        try {
            CompletableFuture<Integer> result = CompletableFuture
                    .supplyAsync(() -> 21, pool)
                    .thenApply(value -> value * 2);
            System.out.println(result.join());
        } finally {
            pool.shutdown();
        }
    }
}

输出 42。supplyAsync() 执行有返回值的任务,runAsync() 执行没有返回值的任务。thenApply() 把上一步结果转换成新的结果。

每次连接通常会得到一个新的阶段。要取得转换后的值,应保存并等待返回的新阶段,而不是仍然读取最初的 Future。

代码在哪条线程执行

没有 Async 后缀的方法,可能由完成前一阶段的线程执行,也可能在注册回调时发现结果已完成,直接由注册线程执行。因此不要根据一次输出就认定回调永远属于某个线程。

带 Async 后缀的方法可以指定执行器。不指定时通常使用公共 ForkJoinPool;Java 8 在公共池不能提供足够并行度时还有创建新线程的后备方式。

耗时阻塞任务最好显式选择合适的执行器,避免占用其他任务也在使用的公共池。前一个任务传了自定义线程池,不代表后续所有不带 executor 参数的 Async 方法都会自动沿用它。

get 与 join

二者都会等待结果。get 用 ExecutionException 包装任务异常,并且可以被中断;join 对异常完成通常抛出 CompletionException,不声明受检的 InterruptedException。

可以用 get(timeout, unit) 限制等待时间。超时会抛出 TimeoutException,任务本身仍可能继续执行,需要另外处理取消。

异常处理

import java.util.concurrent.CompletableFuture;

public class CompletableFailureDemo {
    public static void main(String[] args) {
        CompletableFuture<Integer> source = new CompletableFuture<>();
        CompletableFuture<Integer> fallback = source.exceptionally(error -> -1);
        source.completeExceptionally(new IllegalStateException("模拟失败"));
        System.out.println(fallback.join());
    }
}

输出 -1。exceptionally() 在异常时提供替代值;handle() 同时接收结果和异常,可以转换成新结果;whenComplete() 主要用于观察完成情况,它正常返回时会保留原来的结果或异常。

错误如果已经被转换成默认值,后面看到的就是正常完成,不能再假定调用方还会收到原异常。日志、兜底和错误传播要分清楚。

另外,CompletableFuture 的 cancel(true) 不像 FutureTask 那样负责中断运行中的计算。取消一个结果对象,不能等同于底层操作已经停止。

最后修改:2018 年 04 月 24 日
如果觉得我的文章对你有用,请随意赞赏