CompletableFuture功能介绍与原理分析
前言
Java 1.5版本引入了Future接口,可用于启动一个异步任务,并在后续获得这个异步任务的结果。Future接口可用于两个或多个异步任务的并行执行,提高了编写多线程代码的灵活性。但在实际应用时很难在某些场景下优雅地做到真正的异步,例如,异步任务执行成功后再异步处理回调,监测数个异步任务的执行情况并采取后续处理等等。
作为Java升级变化较大的一个版本,JDK8引入了很多新的特性和工具。而在并发方面,比较重要的特性当属CompletableFuture的引入了。作为Future接口的实现,CompletableFuture相比于FutureTask提供了更灵活的使用方式,来对异步执行的结果和异步线程的处理和调配。本文将基于JDK8,对CompletableFuture的功能、用法及其原理进行介绍。
CompletableFuture功能介绍
CompletableFuture实现了Future接口和CompletionStage接口,CompletableFuture能够实现对异步执行的结果进行异步的处理,得益于对CompletionStage接口方法的实现。例如,调用CompletionStage接口下的thenApply等方法,当CompletableFuture对应的任务结束时,传入thenApply的函数式接口便作为后续的任务得到触发并执行。CompletableFuture的公有方法按功能可大致分为4类:
1.创建CompletableFuture
该类方法用于获取一个CompletableFuture对象实例,其中包括CompletableFuture的构造方法,以及supplyAsync和runAsync。
supplyAsync和runAsync方法可传入一个FunctionalInterface(函数式接口,代表一个可执行函数)。对于supplyAsync,所获得的CompletableFuture即为该函数式接口执行得到的结果的载体。
void completableFutureTest() {
CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> "Hello world");
try {
System.out.println(completableFuture.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
输出:
Hello world
此外,还包括allOf和anyOf方法。allOf方法创建的CompletableFuture以多个CompletableFuture的结果全部返回作为其执行完成的标志,但其本身并不作为任何结果的载体。
void completableFutureAllOfTest() {
CompletableFuture<String> completableFuture1 = CompletableFuture.supplyAsync(() -> {
System.out.println("Hello world1 start!");
sleep(2000);
System.out.println("Hello world1 wake!");
return "Hello world1 finished!";
});
CompletableFuture<String> completableFuture2 = CompletableFuture.supplyAsync(() -> {
System.out.println("Hello world2 start!");
sleep(1000);
System.out.println("Hello world2 wake!");
return "Hello world2 finished!";
});
CompletableFuture completableFutureAll = CompletableFuture.allOf(completableFuture1, completableFuture2);
try {
System.out.println(completableFutureAll.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
输出:
Hello world1 start!
Hello world2 start!
Hello world2 wake!
Hello world1 wake!
null
而anyOf方法创建的CompletableFuture以多个CompletableFuture中的其中任意一个返回作为其完成的标志。
void completableFutureAnyOfTest() {
CompletableFuture<String> completableFuture1 = CompletableFuture.supplyAsync(() -> {
System.out.println("Hello world1 start!");
sleep(2000);
System.out.println("Hello world1 wake!");
return "Hello world1 finished!";
});
CompletableFuture<String> completableFuture2 = CompletableFuture.supplyAsync(() -> {
System.out.println("Hello world2 start!");
sleep(1000);
System.out.println("Hello world2 wake!");
return "Hello world2 finished!";
});
CompletableFuture completableFutureAny = CompletableFuture.anyOf(completableFuture1, completableFuture2);
try {
System.out.println(completableFutureAny.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
输出:
Hello world1 start!
Hello world2 start!
Hello world2 wake!
Hello world2 finished!
Hello world1 wake!
2.CompletableFuture结果的获取
这一类方法会试图获取异步执行的结果:其中get和join会通过阻塞的方式来等待结果;而getNow则会先判断是否已拿到结果,若未拿到则返回传入的默认值。
/**
* Returns the result value (or throws any encountered exception)
* if completed, else returns the given valueIfAbsent.
*
* @param valueIfAbsent the value to return if not completed
* @return the result value, if completed, else the given valueIfAbsent
* @throws CancellationException if the computation was cancelled
* @throws CompletionException if this future completed
* exceptionally or a completion computation threw an exception
*/
public T getNow(T valueIfAbsent) {
Object r;
return ((r = result) == null) ? valueIfAbsent : reportJoin(r);
}
3.CompletableFuture的执行状态控制
该类方法用于控制CompletableFuture的执行,包括cancel、obtrudeValue、obtrudeException、complete和completeExceptionally。其中
1)cancel、complete和completeExceptionally会尝试对未完成的CompletableFuture进行赋值(赋传入值或异常结果)并触发后续任务,若赋值时CompletableFuture已完成则赋值操作无效。
2)obtrudeValue和obtrudeException会直接对CompletableFuture结果进行赋值并触发后续任务,无视CompletableFuture是否执行完成。
4.CompletionStage接口方法的实现
CompletionStage接口由JDK8引入,该接口可以代表一个计算任务,该任务结束时通过调用相应方法可以触发其他计算任务。CompletableFuture作为JDK8中CompletionStage唯一的实现类,靠实现这些方法来对异步执行结果进行灵活地处理。如thenApply方法,当该CompletableFuture执行完成时会继续执行传入thenApply方法的函数式接口。
/**
* Returns a new CompletionStage that, when this stage completes
* normally, is executed with this stage's result as the argument
* to the supplied function.
*
* See the {@link CompletionStage} documentation for rules
* covering exceptional completion.
*
* @param fn the function to use to compute the value of
* the returned CompletionStage
* @param <U> the function's return type
* @return the new CompletionStage
*/
public <U> CompletionStage<U> thenApply(Function<? super T,? extends U> fn);
void completableFutureThenApplyTest() {
CompletableFuture<String> completableFuture1 = CompletableFuture.supplyAsync(() -> {
System.out.println("Hello world1 start!");
return "Hello world1 finished!";
}).thenApply(s -> {
System.out.println(s + " Hello world2 start!");
return "Hello world2 finished!";
});
try {
System.out.println(completableFuture1.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
输出:
Hello world1 start!
Hello world1 finished! Hello world2 start!
Hello world2 finished!
CompletionStage接口声明的方法很多,有thenApply、thenAccept和thenCombine等等,但其实它们大致可以从下述几个维度来进行区分:
1)是否异步执行
该维度决定了当一个CompletionStage完成时,其触发的后续任务是在该调用线程中执行还是启动异步线程来执行。例如,thenApply方法由主线程调用,并由主线程执行传入的Function;而传入thenApplyAsync的Function则会由另起的异步线程执行。
void completableFutureAsyncTest() {
System.out.println("0 " + Thread.currentThread());
CompletableFuture<String> completableFuture1 = CompletableFuture.supplyAsync(() -> {
System.out.println("1 " + "supplyAsync " + Thread.currentThread());
return "";
}).thenApply(s -> {
System.out.println("1 " + "thenApply " + Thread.currentThread());
return "";
});
CompletableFuture<String> completableFuture2 = CompletableFuture.supplyAsync(() -> {
System.out.println("2 " + "supplyAsync " + Thread.currentThread());
return "";
}).thenApplyAsync(s -> {
System.out.println("2 " + "thenApplyAsync " + Thread.currentThread());
return "";
});
}
输出:
0 Thread[main,5,main]
1 supplyAsync Thread[ForkJoinPool.commonPool-worker-9,5,main]
1 thenApply Thread[main,5,main]
2 supplyAsync Thread[ForkJoinPool.commonPool-worker-9,5,main]
2 thenApplyAsync Thread[ForkJoinPool.commonPool-worker-9,5,main]
可以看到,传入thenApply方法的Function在主线程下执行,而传入thenApplyAsync的Function则在ForkJoinPool管理调度的异步线程下执行。
不过关于thenApply方法由主线程调用,并由主线程执行,其实是存在例外的。就是当thenApply的前置任务未异步执行完成时,主线程在调用thenApply时,传入thenApply的Function无法在主线程等待其前置任务完成,只好暂时搁置,等待前置任务执行完成后另起线程执行。这部分原理会在后面原理分析时详述。这里只需明确thenApply会优先由调用thenApply方法的线程执行,而thenApplyAsync会直接分配给异步线程执行。
2)输入参数与输出结果
即方法接收的FunctionalInterface支不支持传入参数,支持传入几个参数,支不支持输出结果。例如,thenApply支持单输入单输出,因为thenApply方法的参数为Function类型的函数式接口,是既有输入又有输出的;而thenAccept则只支持单输入而不能输出,因为thenAccept方法的参数为Consumer类型的函数式接口,只有输入没有输出;thenRun则即无输入也无输出。
/**
* Returns a new CompletionStage that, when this stage completes
* normally, is executed with this stage's result as the argument
* to the supplied action.
*
* See the {@link CompletionStage} documentation for rules
* covering exceptional completion.
*
* @param action the action to perform before completing the
* returned CompletionStage
* @return the new CompletionStage
*/
public CompletionStage<Void> thenAccept(Consumer<? super T> action);
void completableFutureThenAcceptTest() {
CompletableFuture<Void> completableFuture1 = CompletableFuture.supplyAsync(() -> {
System.out.println("Hello world1 start!");
return "Hello world1 finished!";
}).thenAccept(s -> {
System.out.println(s + " Hello world2 start!");
});
try {
System.out.println(completableFuture1.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
输出:
Hello world1 start!
Hello world1 finished! Hello world2 start!
null
/**
* Returns a new CompletionStage that, when this stage completes
* normally, executes the given action.
*
* See the {@link CompletionStage} documentation for rules
* covering exceptional completion.
*
* @param action the action to perform before completing the
* returned CompletionStage
* @return the new CompletionStage
*/
public CompletionStage<Void> thenRun(Runnable action);
void completableFutureThenRunTest() {
CompletableFuture<Void> completableFuture1 = CompletableFuture.supplyAsync(() -> {
System.out.println("Hello world1 start!");
return "Hello world1 finished!";
}).thenRun(() -> System.out.println( "Hello world2 start!"));
try {
System.out.println(completableFuture1.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
输出:
Hello world1 start!
Hello world2 start!
null
3)依赖的CompletionStage个数
当CompletionStage调用thenApply方法时,传入的Function是在CompletionStage得到结果后执行的,所以thenApply方法的执行依赖了一个CompletionStage的结果。而thenCombine方法则需要再传入一个CompletionStage,当调用thenCombine的CompletionStage和传入的这个CompletionStage都得到结果后,才继续执行传入thenCombine的FunctionalInterface,所以thenCombine依赖了两个CompletionStage的结果。
void completableFutureThenCombineTest() {
CompletableFuture<String> completableFuture1 = CompletableFuture.supplyAsync(() -> {
System.out.println("Hello world1 start!");
return "Hello world1 finished!";
});
CompletableFuture<String> completableFuture2 = CompletableFuture.supplyAsync(() -> {
System.out.println("Hello world2 start!");
return "Hello world2 finished!";
}).thenCombine(completableFuture1, (r2, r1) -> {
System.out.println("Hello world combine start!");
return r1 + " " + r2;
});
try {
System.out.println(completableFuture2.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
输出:
Hello world1 start!
Hello world2 start!
Hello world combine start!
Hello world1 finished! Hello world2 finished!
thenCombine需要等待两个CompletionStage都得到结果,而applyToEither则只需要两个CompletionStage其中一个得到结果,即可触发后续执行。
void completableFutureApplyToEitherTest() {
CompletableFuture<String> completableFuture1 = CompletableFuture.supplyAsync(() -> {
System.out.println(&