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 那样负责中断运行中的计算。取消一个结果对象,不能等同于底层操作已经停止。