CompletableFuture
为什么会有CompletableFuture
在CompletableFuture未开发出来前,使用Java进行获取异步编程的结果是很不方便的,通常使用Future这个类获取结果,但是是Future是Java 5添加的类,用来描述一个异步计算的结果。你可以使用isDone
方法检查计算是否完成,或者使用get
阻塞住调用线程,直到计算完成返回结果,你也可以使用cancel
方法停止任务的执行。
虽然Future
以及相关使用方法提供了异步执行任务的能力,但是对于结果的获取却是很不方便,只能通过阻塞或者轮询的方式得到任务的结果。阻塞的方式显然和我们的异步编程的初衷相违背,轮询的方式又会耗费无谓的CPU资源,而且也不能及时地得到计算结果,为什么不能用观察者设计模式当计算结果完成及时通知监听者呢?
所以在Java 8中, 新增加了一个包含50个方法左右的类: CompletableFuture,提供了非常强大的Future的扩展功能,可以帮助我们简化异步编程的复杂性,提供了函数式编程的能力,可以通过回调的方式处理计算结果,并且提供了转换和组合CompletableFuture的方法。
CompletableFuture实现了Future和CompletionStage两个接口。
Future接口相对简单些,提供了取消(cancel)、获取结果(get)、检测是否完成或者取消(isCancelled、isDone)这些方法,Future接口提供了异步获取结果的能力。但是Future没有提供通知机制,Future是否执行完任务需要不断的检查或者调用get()方法阻塞任务执行,所以CompletableFuture还实现了CompletionStage。CompletionStage相当于阶段,一个CompletionStage对象是异步计算中的一个阶段,当一个CompletionStage完成时会触发下一个动作或计算。前一个任务执行成功后可以自动触发下一个任务的执行,中间无需等待。相当于实现了异步任务的通知机制
CompletableFuture还能够将任务放到不同的线程中执行,既可以在当前线程中直接执行任务,也可以将其放到其他任务线程中执行,这个过程是自动的,无需干预。
使用方法
- CompletableFuture类实现了CompletionStage和Future接口,所以你还是可以像以前一样通过阻塞或者轮询的方式获得结果,尽管这种方式不推荐使用。
public class BasicMain {
public static CompletableFuture<Integer> compute() {
final CompletableFuture<Integer> future = new CompletableFuture<>();
return future;
}
public static void main(String[] args) throws Exception {
final CompletableFuture<Integer> f = compute();
class Client extends Thread {
CompletableFuture<Integer> f;
Client(String threadName, CompletableFuture<Integer> f) {
super(threadName);
this.f = f;
}
@Override
public void run() {
try {
System.out.println(this.getName() + ": " + f.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
new Client("Client1", f).start();
new Client("Client2", f).start();
System.out.println("waiting");
f.complete(100);
//f.completeExceptionally(new Exception());
System.in.read();
// 控制台打印
// waiting
// Client1: 100
// Client2: 100
}
}
可以看到get方法阻塞了client1和client2,当 f.complete(100)计算完成时,client1和client2才输出。
-
CompletableFuture.completedFuture
是一个静态辅助方法,用来返回一个已经计算好的CompletableFuture
。public static <U> CompletableFuture<U> completedFuture(U value)//value就代表线程执行run返回回去的值
创建异步对象
而以下四个静态方法用来为一段异步执行的代码创建CompletableFuture
对象:
public static CompletableFuture<Void> runAsync(Runnable runnable)
public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)
以Async
结尾代表异步执行
有没有Executor代表是使用接口提供给的线程池,还是自定义的
Runnable
类型的参数会忽略计算的结果
Supplier
类型类型的参数会返回计算的结果,U代表CompletableFuture
的计算结果类型
异步执行完成的回调
public CompletableFuture<T> whenComplete(BiConsumer<? super T,? super Throwable> action)
public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T,? super Throwable> action)
public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T,? super Throwable> action, Executor executor)
public CompletableFuture<T> exceptionally(Function<Throwable,? extends T> fn)
Consumer
是只处理计算结果不返回处理后的值
?BiConsumer
会组合另外一个CompletionStage
只处理计算结果,这里相当于结合上一个CompletableFuture处理结果
例子:
public void testAcceptrun() throws ExecutionException, InterruptedException {
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
return 100;
});
CompletableFuture<Void> f = future.thenAcceptBoth(CompletableFuture.completedFuture(10), (x, y) -> System.out.println(x * y));
System.out.println(f.get());
f.runAfterBoth(CompletableFuture.completedFuture(10), new Runnable() {
@Override
public void run() {
System.out.println("不使用组合返回的结果");
}
});
//1000
// null
// 不使用组合返回的结果
}
#### 多异步任务的组合处理
public CompletableFuture thenCompose(Function<? super T,? extends CompletionStage> fn)
public CompletableFuture thenComposeAsync(Function<? super T,? extends CompletionStage> fn)
public CompletableFuture thenComposeAsync(Function<? super T,? extends CompletionStage> fn, Executor executor)
这一组方法接受一个Function作为参数,这个Function的输入是当前的CompletableFuture的计算值,返回结果将是一个新的CompletableFuture,这个新的CompletableFuture会组合原来的CompletableFuture和函数返回的CompletableFuture。
记住,thenCompose方法会在某个任务执行完成后,将该任务的执行结果作为方法入参然后执行指定的方法,该方法会返回一个新的CompletableFuture实例。
CompletableFuture future = CompletableFuture.supplyAsync(() -> {
return 100;
});
CompletableFuture f = future.thenCompose( i -> {
return CompletableFuture.supplyAsync(() -> {
return (i * 10) + “”;
});
});
System.out.println(f.get()); //1000
而下面的一组方法`thenCombine`用来复合另外一个CompletionStage的结果。它的功能类似:
两个CompletionStage是并行执行的,它们之间并没有先后依赖顺序,`other`并不会等待先前的`CompletableFuture`执行完毕后再执行。
public <U,V> CompletableFuture thenCombine(CompletionStage<? extends U> other, BiFunction<? super T,? super U,? extends V> fn)
public <U,V> CompletableFuture thenCombineAsync(CompletionStage<? extends U> other, BiFunction<? super T,? super U,? extends V> fn)
public <U,V> CompletableFuture thenCombineAsync(CompletionStage<? extends U> other, BiFunction<? super T,? super U,? extends V> fn, Executor executor)
其实从功能上来讲,它们的功能更类似`thenAcceptBoth`,只不过`thenAcceptBoth`是纯消费,它的函数参数没有返回值,而`thenCombine`的函数参数`fn`有返回值。
CompletableFuture future = CompletableFuture.supplyAsync(() -> {
return 100;
});
CompletableFuture future2 = CompletableFuture.supplyAsync(() -> {
return “abc”;
});
CompletableFuture f = future.thenCombine(future2, (x,y) -> y + “-” + x);
System.out.println(f.get()); //abc-100
thenCompose和thenCombine的区别:
- thenCompose 可以用于组合多个CompletableFuture,将前一个任务的返回结果作为下一个任务的参数,它们之间存在着先后顺序。
- thenCompose方法会在某个任务执行完成后,将该任务的执行结果作为方法入参然后执行指定的方法,该方法会返回一个新的CompletableFuture实例。
- thenCombine两个任务中只要有一个执行异常,则将该异常信息作为指定任务的执行结果。
- thenCombine两个任务是并行执行的,它们之间并没有先后依赖顺序。
**Either**
Either顾名思义是当任意一个CompletableFuture计算完成的时候就会执行。
public CompletableFuture acceptEither(CompletionStage<? extends T> other, Consumer<? super T> action)
public CompletableFuture acceptEitherAsync(CompletionStage<? extends T> other, Consumer<? super T> action)
public CompletableFuture acceptEitherAsync(CompletionStage<? extends T> other, Consumer<? super T> action, Executor executor)
public CompletableFuture applyToEither(CompletionStage<? extends T> other, Function<? super T,U> fn)
public CompletableFuture applyToEitherAsync(CompletionStage<? extends T> other, Function<? super T,U> fn)
public CompletableFuture applyToEitherAsync(CompletionStage<? extends T> other, Function<? super T,U> fn, Executor executor)
`accept`代表异步执行完执行消费,这个方法返回`CompletableFuture<Void>
`applyTo`方法代表转换,只是从CompletableFuture(T)-->CompletableFuture(U),CompletableFuture不变
下面这个例子有时会输出`100`,有时候会输出`200`,哪个Future先完成就会根据它的结果计算。
public void testapplyToEither() throws ExecutionException, InterruptedException {
Random rand = new Random();
CompletableFuture future = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(1000 + rand.nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
return 100;
});
CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(1000 + rand.nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
return 200;
});
CompletableFuture<String> f = future.applyToEither(future2,i -> i.toString());
System.out.println(f.get());//200
}
#### 辅助方法 `allOf` 和 `anyOf`
前面我们已经介绍了几个静态方法:`completedFuture`、`runAsync`、`supplyAsync`,下面介绍的这两个方法用来组合多个CompletableFuture。
public static CompletableFuture allOf(CompletableFuture<?>… cfs)
public static CompletableFuture anyOf(CompletableFuture<?>… cfs)
`allOf`方法是当所有的`CompletableFuture`都执行完后执行计算。
`anyOf`方法是当任意一个`CompletableFuture`执行完后就会执行计算,计算的结果是第一个执行完的CompletableFuture的计算结果。
Random rand = new Random();
CompletableFuture future1 = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(10000 + rand.nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
return 100;
});
CompletableFuture future2 = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(10000 + rand.nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
return “abc”;
});
//CompletableFuture f = CompletableFuture.allOf(future1,future2);
CompletableFuture f = CompletableFuture.anyOf(future1,future2);
System.out.println(f.get());//100
原理
下面以如下代码为例介绍CompletableFuture的实现原理。
public static void main(String[] args) throws Exception {
CompletableFuture f=CompletableFuture.supplyAsync(()->{
System.out.println("实验");
return "1";
}).thenAccept((x)->{
System.out.println(x);
});
}
先来看一下静态方法supplyAsync():
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier) {
//asyncPool是一个线程池,
//它根据配置或者CPU的个数来决定是使用ForkJoinPool还是ThreadPerTaskExecutor作为线程池的实现
return asyncSupplyStage(asyncPool, supplier);
}
static <U> CompletableFuture<U> asyncSupplyStage(Executor e,
Supplier<U> f) {
if (f == null) throw new NullPointerException();
//创建一个新的CompletableFuture对象,所以后面调用thenAccept()方法其实使用的是这个新建的对象
CompletableFuture<U> d = new CompletableFuture<U>();
//在线程池中执行AsyncSupply异步任务
e.execute(new AsyncSupply<U>(d, f));
return d;
}
CompletableFuture提供了属性asyncPool记录线程池对象,当调用静态方法时,CompletableFuture都是使用该线程池来执行异步任务,如果自己传了线程池,则使用自定义的线程池
asyncSupplyStage()方法返回一个新建的CompletableFuture对象,在CompletableFuture中大部分public方法都会返回一个新建的CompletableFuture对象,所以在链式调用中每次调用的方法都是新建对象的方法,而不是最初那个对象的。
最后将AsyncSupply()对象放入了线程池中,下面来看一下AsyncSupply.run()方法的实现:
public void run() {
CompletableFuture d; Supplier f;
//dep是在asyncSupplyStage()方法中新建的那个CompletableFuture对象
//fn是自定义的Supplier对象
if ((d = dep) != null && (f = fn) != null) {
dep = null; fn = null;
//CompletableFuture的result属性表示Supplier对象的执行结果,
//更准确的说,result属性是用来记录我们编写的lambda表达式的运算结果的
if (d.result == null) {
try {
//使用CAS将Supplier的执行结果放入到属性result中
d.completeValue(f.get());
} catch (Throwable ex) {
//如果抛出异常,将异常对象记录到属性result
d.completeThrowable(ex);
}
}
//下面详细介绍postComplete()方法
d.postComplete();
}
//postComplete()方法的作用是检查是否有下一个任务需要执行,如果需要便会触发该任务的执行
//每调用一个CompletableFuture实例方法,CompletableFuture都将该实例方法要执行的任务封装成一个Completion对象,
//并将Completion对象压入到当前CompletableFuture对象的栈中(栈顶使用stack属性记录),
//也就是将后一个CompletableFuture对象的任务压入到前一个对象的栈中,
//每次执行完当前任务后,都会调用postComplete()方法检查栈顶是否有待执行任务
final void postComplete() {
CompletableFuture<?> f = this; Completion h;
//循环检查栈顶是否有待执行任务
while ((h = f.stack) != null ||
(f != this && (h = (f = this).stack) != null)) {
CompletableFuture<?> d; Completion t;
//casStack()将栈顶切换为下一个次栈顶元素
if (f.casStack(h, t = h.next)) {
if (t != null) {
if (f != this) {
//当后一个CompletableFuture对象的栈中的任务需要嵌入到当前栈中执行时,
//postComplete()获取到这些任务并调用pushStack()放入自己的栈中,
//然后在本方法里面执行这些任务
pushStack(h);
continue;
}
h.next = null; // detach
}
//如果栈中有待执行任务,调用tryFire()方法执行该任务,
//如果tryFire()的入参为NESTED(值为-1),且返回的不是null,
//说明后一个CompletableFuture对象的栈中的任务需要嵌入执行
f = (d = h.tryFire(NESTED)) == null ? this : d;
}
}
}
run()方法执行了当前Supplier对象,并记录下执行结果。之后调用postComplete()检查栈顶是否有待执行任务,后面介绍thenAccept()方法的实现原理时,我们可以看到任务是如何被放到栈中的。这里还有一点需要注意,调用tryFire()方法时,如果入参为NESTED,表示后一个CompletableFuture的栈中的任务需要嵌入到当前线程中(或者说是嵌入到当前栈中)执行,这相当于当前线程执行完了所有任务后,检查到后一个对象的栈中还有任务,那么便“偷”一个任务过来执行。
tryFire()有多个不同的实现,这里不再一一介绍源码。
下面来看一下thenAccept()方法的实现原理。
public CompletableFuture<Void> thenAccept(Consumer<? super T> action) {
return uniAcceptStage(null, action);
}
private CompletableFuture<Void> uniAcceptStage(Executor e,
Consumer<? super T> f) {
if (f == null) throw new NullPointerException();
//新建一个CompletableFuture对象d,本方法将该对象返回给调用方
CompletableFuture<Void> d = new CompletableFuture<Void>();
//uniAccept()检查前一个任务是否执行完成,如果执行成功了,那么执行Consumer对象
//注意:入参this指的是supplyAsync()方法里面创建的CompletableFuture对象
//这里逻辑比较绕,一定分清楚现在调用的thenAccept()方法是属于是supplyAsync()方法里面创建的CompletableFuture对象的
if (e != null || !d.uniAccept(this, f, null)) {
UniAccept<T> c = new UniAccept<T>(e, d, this, f);
//将UniAccept对象压入到CompletableFuture对象的栈中
//这个栈是supplyAsync()方法里面创建的CompletableFuture对象的
//push()方法使用CAS将对象c压入到栈
push(c);
c.tryFire(SYNC);
}
return d;
}
final <S> boolean uniAccept(CompletableFuture<S> a,
Consumer<? super S> f, UniAccept<S> c) {
Object r; Throwable x;
//a.result表示上一个任务的执行结果,如果为null,表示任务还在执行中
//上一个任务没有执行完,直接退出当前方法
if (a == null || (r = a.result) == null || f == null)
return false;
//result = null表示当前任务还没执行
//能够执行下面的逻辑,说明上一个任务执行完毕,可以执行当前任务了
tryComplete: if (result == null) {
if (r instanceof AltResult) {
if ((x = ((AltResult)r).ex) != null) {
completeThrowable(x, r);
break tryComplete;
}
r = null;
}
try {
if (c != null && !c.claim())
return false;
@SuppressWarnings("unchecked") S s = (S) r;
//执行Consumer任务
f.accept(s);
//向result属性设置结果
completeNull();
} catch (Throwable ex) {
completeThrowable(ex);
}
}
return true;
}
thenAccept()方法首先检查前一个CompletableFuture的任务是否执行完成,如果没有,则创建UniAccept对象,并将该对象压入栈中,如果执行完成,则执行Consumer任务,并设置执行结果。设置执行结果使用completeNull()方法,该方法通过CAS将NIL对象设置到result属性,NIL表示的是空结果,当任务返回null或者任务没有执行结果时,都会设置result=NIL
static final AltResult NIL = new AltResult(null);
在上面代码中,UniAccept对象压入栈之后,便调用UniAccept.tryFire()方法,下面来看一下该方法的实现原理:
final CompletableFuture<Void> tryFire(int mode) {
CompletableFuture<Void> d; CompletableFuture<T> a;
//dep表示thenAccept()方法中新建的CompletableFuture对象d
//src表示supplyAsync()方法中创建的CompletableFuture对象
if ((d = dep) == null ||
//uniAccept()在上面已经介绍过了
//这里调用uniAccept()方法可以确保在多线程环境下,Consumer对象已经可以被执行
!d.uniAccept(a = src, fn, mode > 0 ? null : this))
return null;
dep = null; src = null; fn = null;
//postFire()做收尾工作,检查对象a和对象d的栈是否有待执行任务,如果有分别调用postComplete()方法,
//在调用前还会检查mode的值,如果mode为NESTED(-1),则说明栈的任务由别的线程执行,不再执行
return d.postFire(a, mode);
}
final CompletableFuture<T> postFire(CompletableFuture<?> a, int mode) {
if (a != null && a.stack != null) {
if (mode < 0 || a.result == null)
a.cleanStack();//清理栈,将执行过的任务从栈中清除
else
a.postComplete();//执行栈的任务,就本小节的例子来看,a栈的任务都已经执行过了
}
if (result != null && stack != null) {
if (mode < 0)
return this;//直接返回当前CompletableFuture对象,该对象的栈任务由其他线程执行
else
postComplete();//执行栈任务
}
return null;
}
到这里本小节示例代码的执行过程介绍完毕,下面总结一下。
每个CompletableFuture的方法都会新建CompletableFuture对象,并将当前方法需要执行的任务封装成Completion对象,之后将Completion压入到上个方法中创建的CompletableFuture对象的栈中,这样每个CompletableFuture对象的栈都保存了下一个待执行的任务,通过栈将每个任务串在一起,形成一个链条。当执行完当前CompletableFuture对象的任务后,接着查看栈中是否有任务,如果有则直接执行,如果没有就退出。后一个CompletableFuture对象也是一样,首先将任务压入前一个对象的栈中,检查前一个任务是否执行完成,如果没有则退出,如果前一个任务执行完成,则查看栈中的任务,然后执行当前任务