前言
CompletableFuture 实现了CompletionStage接口和Future接口,前者是对后者的一个扩展,增量了异步回调,流式处理,多个Future组合处理的能力,使Java在处理多任务的协同工作时更加顺畅便利。
使用Future获得异步执行结果时,要么调用阻塞方法 get(),要么轮询看 isDone()是否为true,这两种方法都不是很好,因为主线程也会被迫等待。从Java 8 开始引入了 CompletableFuture,它针对Future做了改进,可以传入回调对象,当异步任务完成或者发生异常时,自动调用回调对象的回调方法。
CompletableFuture 使用
我们以获取股票价格为例,看看如何使用CompletableFuture:
import java.util.concurrent.CompletableFuture;
public class Main {
public static void main(String[] args) throws Exception {
// 创建异步执行任务:
CompletableFuture<Double> cf = CompletableFuture.supplyAsync(Main::fetchPrice);
// 如果执行成功:
cf.thenAccept((result) -> {
System.out.println("price: " + result);
});
// 如果执行异常:
cf.exceptionally((e) -> {
e.printStackTrace();
return null;
});
// 主线程不要立刻结束,否则CompletableFuture默认使用的线程池会立刻关闭:
Thread.sleep(200);
}
static Double fetchPrice() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
if (Math.random() < 0.3) {
throw new RuntimeException("fetch price failed!");
}
return 5 + Math.random() * 20;
}
}
创建一个 CompletableFuture 是通过CompletableFuture.supplyAsync()实现的,它需要实现了Supplier接口的对象:
public interface Supplier<T> {
T get();
}
用lambda语法简化了一下,直接传入 Main:fetchPrice,因为Main:fetchPrice()静态方法的签名符合Supplier接口的定义(除了方法名外)。
然后,CompletableFuture已经被提交给默认的线程池执行了,我们需要定义的是CompletableFuture完成时和异常时需要回调的实例。完成时,CompletableFuture会调用Comsumer对象:
public interface Consumer<T> {
void accept(T t);
}
异常时,CompletableFuture会调用Function对象:
public interface Function<T, R> {
R apply(T t);
}
都使用了lambda语法简化了代码。
可见 CompletableFuture的优点是:
- 异步任务结束时,会自动回调某个对象的方法
- 异步任务出错时,会自动回调某个对象的方法
- 主线程设置好回调后,不再关心异步任务的执行
如果只是实现了异步回调机制,我们还看不出CompletableFuture相比Future的优势。CompletableFuture更强大的功能是,多个CompletableFuture可以串行执行,例如,定义两个CompletableFuture,第一个CompletableFuture根据证券名称查询证券代码,第二个CompletableFuture根据证券代码查询证券价格,这两个CompletableFuture实现串行操作如下:
public class Main {
public static void main(String[] args) throws InterruptedException {
//第一个任务
CompletableFuture<String> cfQuery = CompletableFuture.supplyAsync(() -> {
return queryCode("中国石油");
});
//cgQuery成功后继续执行下一个任务
CompletableFuture<Double> cfFetch = cfQuery.thenApplyAsync((code) -> {
return fetchPrice(code);
});
//cfFetch成功后打印结果:
cfFetch.thenAccept((result) -> {
System.out.println("price:" + result);
});
//主线程不要立刻结束,否则CompletableFuture默认使用的线程池会立刻关闭
Thread.sleep(200);
}
static String queryCode(String name) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
return "601857";
}
static Double fetchPrice(String code) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
return 5 + Math.random() * 20;
}
}
上述逻辑实现的异步查询规则实际上是:
除了 anyOf()可以实现任意个 CompletableFuture 只要一个成功,allOf()可以实现所有 CompletableFuture 都必须成功,这些组合操作可以实现非常复杂的异步流程控制。
最后我们注意CompletableFuture的命名规则:
- xxx():表示该方法将继续在已有的线程中执行
- xxxAsync():表示将异步在线程池中执行