想了解更多JUC的知识
——JUC并发编程合集
10. CompletableFuture
1. Future接口(前言)
1.1 概述
- Future 接口在Java 5中被引入,设计初衷是对将来某个时刻会发生的结果进行建模。它建模了一种异步计算,返回一个执行运算结果的引用,当运算结束后,这个引用被返回给调用方。
- 在Future 中触发那些潜在的耗时的操作把调用线程解放出来,让它能继续执行其他有价值的工作,不再需要等待耗时的操作完成。
- Future 的另一个优点是它比更底层的 Thread 更易用。要使用 Future ,通常你只需要将耗时的操作封装在一个 Callable 对象中,再将它提交给 ExecutorService 。
1.2 方法说明
- boolean
cancel(boolean mayInterruptIfRunning)
- 方法描述:尝试取消此任务的执行。如果任务已完成、已被取消或由于某些其他原因无法取消,则此尝试将失败。如果成功,并且在调用取消时此任务尚未启动,则此任务不应该运行。如果任务已经开始,则 mayInterruptIfRunning 参数确定是否应该中断执行该任务的线程以尝试停止该任务。
- 注意:此方法返回后,对 isDone() 的后续调用将始终返回 true。如果此方法返回 true,则后续调用 isCancelled() 将始终返回 true。
- 参数mayInterruptIfRunning:如果执行此任务的线程应该被中断,则为 true;否则,允许完成正在进行的任务
- 返回值:如果任务无法取消,则返回 false,通常是因为它已经正常完成;否则为真
- boolean
isCancelled()
- 方法描述:如果此任务在正常完成之前被取消,则返回 true;否则返回false
- V
get()
- 方法描述:如有必要,等待计算完成,然后返回其结果
- 注意:get方法需要抛出ExecutionException和InterruptedException异常
- V
get(long timeout,TimeUnit unit)
- 方法描述:如有必要,最多等待给定时间以完成计算,然后返回其结果(如果可用)
- 参数
- long timeout:最长等待时间
- TimeUnit unit:时间单位
- 注意:get方法需要抛出ExecutionException和InterruptedException异常
- boolean
isDone()
- 方法描述:如果此任务完成,则返回 true(完成可能是由于正常终止、异常或取消——在所有这些情况下,此方法都将返回 true);否则返回false。
1.3 案例
- 使用 Future 以异步的方式执行一个耗时的操作
public class FutureTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建线程池
ExecutorService threadPool = Executors.newCachedThreadPool();
//以异步的方式在新的线程中执行耗时的任务
Future<String> future = threadPool.submit(new Callable<String>() {
@Override
public String call() throws Exception {
try {
TimeUnit.SECONDS.sleep(3);//模拟一个耗时的任务
} catch (InterruptedException e) {
e.printStackTrace();
}
return "I'm OK";
}
});
//异步操作进行的同时,main线程可以做其他的事
System.out.println(Thread.currentThread().getName() + "正在做其他工作");
//轮询
while (true){
if(future.isDone()){
System.out.println(future.get());
break;
}else {
//暂停毫秒
try {
TimeUnit.MILLISECONDS.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("正在处理中,请稍后!");
}
}
//关闭线程池
threadPool.shutdown();
}
}
- 这种编程方式让你的线程可以在 ExecutorService 以并发方式调用另一个线程执行耗时操作的同时,去执行一些其他的任务。
- 接着,如果你已经运行到没有异步操作的结果就无法继续任何有意义的工作时,可以调用它的 get 方法去获取操作的结果。
- 如果操作已经完成,该方法会里立刻返回操作的结果,否则它会阻塞你的线程,直到操作完成,返回相应的结果。
- 存在的问题:如果该耗时任务永远不返回了怎么办?为了处理这种可能性,Future 提供了重载版本的 get 方法,它接受一个超时的参数,通过它,可以定义你的线程等待 Future 结果的最长时间,而不是一直等待下去。
1.4 Future的优点与缺点
-
优点:能够减少程序的运行时间,因为能够同时跑很多个线程并接收返回值。
-
缺点:
-
Future对于结果的获取不是很友好。当执行get()方法的时候,它有可能阻塞,一直等到有结果返回,当然还可以传入一个超时时间,如果超过时间,会抛出异常。在我们进行使用的时候,一般会有两种写法:
-
指定超时时间并捕获异常处理
try{ future.get(3,TimeUnit.SECOND); }catch(TimeoutException e){ //对应的操作 }
-
轮询获取
while (true){ if(future.isDone()){ System.out.println(future.get()); break; }else { //暂停毫秒 try { TimeUnit.MILLISECONDS.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("正在处理中,请稍后!"); } }
-
-
不能对任务进行优先返回
例如:现在有三个任务,第一个任务耗时10s,第二个任务耗时3s,第三个任务耗时5s,我们将其提交给线程池,并将返回结果放到一个list中去依次获取。
其实我们已经知道第二、三个任务会优先执行完毕,但是却必须等第一个任务get()出结果后才能继续处理,这种在一些场景下就会浪费时间了。
-
2. 背景
- 对于简单的业务场景使用Future完全OK,但是应对一些复杂的任务,Futrue有自身的一些缺点(阻塞的方式和异步编程的设计理念相违背,二轮询的方式会耗费无畏的CPU资源),JDK 8中引出了CompletableFuture。
- 想完成一些复杂的任务:
- 回调通知:应对Future的完成时间,完成了再通知我们,也就是回调通知。通过轮询的方式去判断任务是否完成,这样的方式非常占CPU,而且代码也不美观。
- 创建异步任务:Future+线程池配合使用,能显出提高程序的执行效率
- 多个任务前后依赖可以组合处理:想将多个异步任务的计算结果组合起来,后一个异步任务的计算结果需要前一个异步任务的值,将两个或多个异步任务计算合成一个异步任务计算,这几个异步任务计算互相独立,同时后面这个又依赖前一个处理的结果
- 对计算速度选最快的:当Future集合中某个任务最快结束时,返回结果,返回第一名处理结果
3. 概述
-
CompletableFuture类实现了Future和CompletionStage接口
-
CompletionStage接口说明:
- CompletionStage代表异步计算过程中的某一个阶段,一个阶段完成后可能会触发另外一个阶段
- 一个阶段的计算执行可以是一个Funcation、Consumer、Runnable。
- 比如:stage.thenApply (x->square(x)).thenAccept(x->System.out.println(x)).thenRun(()->{System.out.println()});
- 一个阶段的执行可能是被单个阶段的完成触发,也可能是由多个阶段一起触发.有些类似Linux系统的管道分隔符传参数
-
CompletableFuture类说明:
- 在Java 8中,CompletableFuture提供了非常强大的Future的扩展功能,可以帮助我们简化异步编程的复杂性,并且提供了函数式编程的能力,可以通过回调的方式处理计算结果,也提供了转换和组合CompletableFuture的方法
- 它可能代表一个明确完成的Future,也有可能代表一个完成阶段(CompletionStage),它支持在计算完成以后触发一些函数或执行某些动作
4. 创建方式
-
CompletableFuture提供了四个核心的静态方法来创建一个异步操作
-
runAsync不支持返回值
①
public static CompletableFuture<Void> runAsync(Runnable runnable)
②public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor)
-
supplyAsync可以支持返回值。(我们一般用supplyAsync来创建)
③
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
④public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)-
-
②和④方法中有两个参数,第二个参数是传入一个线程池,没有指定Executor的方法会默认使用ForkJoinPool.commonPool() 作为它的线程池执行异步代码;如果指定线程池,则使用指定的线程池运行。
public class CompletableFutureTest { public static void main(String[] args) throws ExecutionException, InterruptedException { //自定义线程池 ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 5, 2L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(3), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy()); //①runAsync(Runnable runnable) CompletableFuture<Void> future1 = CompletableFuture.runAsync(() -> { System.out.println(Thread.currentThread().getName() + ":future1 is OK"); }); System.out.println(future1.get());//null //②runAsync(Runnable runnable, Executor executor) CompletableFuture<Void> future2 = CompletableFuture.runAsync(() -> { System.out.println(Thread.currentThread().getName() + ":future2 is OK"); }, executor); System.out.println(future2.get());//null //③supplyAsync(Supplier<U> supplier) Comple supplyAsync(Supplier<U> supplier)`tableFuture<String> future3 = CompletableFuture.supplyAsync(()->{ System.out.println(Thread.currentThread().getName() + ":future3 is OK"); return "hello,supplyAsync"; }); System.out.println(future3.get()); //④supplyAsync(Supplier<U> supplier, Executor executor) CompletableFuture<String> future4 = CompletableFuture.supplyAsync(()->{ System.out.println(Thread.currentThread().getName() + ":future3 is OK"); return "hello,supplyAsync"; },executor); System.out.println(future4.get()); executor.shutdown(); } }
结果:
-
5. 相关API
5.1 获得结果和触发计算
- 获得结果:
- public T
get( )
:只要调用了get( )方法,不管是否计算完成都会导致阻塞,会抛出异常 - public T
get(long timeout, TimeUnit unit)
:过时不候 - public T
getNow(T valuelfAbsent)
:返回计算完成后的结果;没有计算完成的情况下返回设定的valuelfAbsent作为替代 - public T
join( )
:join方法和get( )方法作用一样,不同的是join方法不抛出异常
- public T
- 主动触发计算:
- public boolean
complete(T value)
:是否打断get方法立刻返回括号值
- public boolean
5.2 对计算结果进行处理
-
thenApply
:计算结果存在依赖关系,这两个线程串行化,由于存在依赖关系,当前步骤有异常的话就不再执行后面的代码public <U> CompletableFuture<U> thenApply( Function<? super T,? extends U> fn) { return uniApplyStage(null, fn); }
-
handle
:有异常也可以往下一步走,根据带的异常参数可以进一步处理public <U> CompletableFuture<U> handle( BiFunction<? super T, Throwable, ? extends U> fn) { return uniHandleStage(null, fn); }
-
whenComplete
:是执行当前任务的线程执行完后,继续执行whenComplete的任务public CompletableFuture<T> whenComplete( BiConsumer<? super T, ? super Throwable> action) { return uniWhenCompleteStage(null, action); }
-
whenCompleteAsync
:执行完当前任务后把whenCompleteAsync这个任务继续提交给线程池来进行执行public CompletableFuture<T> whenCompleteAsync( BiConsumer<? super T, ? super Throwable> action) { return uniWhenCompleteStage(asyncPool, action); }
5.3 对计算结果进行消费
-
thenRun
:任务A执行完执行B,并且B不需要A的结果public CompletableFuture<Void> thenRun(Runnable action) { return uniRunStage(null, action); }
-
thenAccept
:任务A执行完成执行B,B需要A的结果,但是任务B无返回值public CompletableFuture<Void> thenAccept(Consumer<? super T> action) { return uniAcceptStage(null, action); }
-
thenApply
:任务A执行完成执行B,B需要A的结果,同时任务B有返回值public <U> CompletableFuture<U> thenApply( Function<? super T,? extends U> fn) { return uniApplyStage(null, fn); }
-
线程串行化方法:带了
Async
的方法表示的是:会重新在线程池中启动一个线程来执行任务public <U> CompletableFuture<U> thenApply(Function<? super T,? extends U> fn) public <U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn) public <U> CompletableFuture<U> thenApplyAsync (Function<? super T,? extends U> fn, Executor executor) public CompletableFuture<Void> thenAccept(Consumer<? super T> action) public CompletableFuture<Void> thenAcceptAsync(Consumer<? super T> action) public CompletableFuture<Void> thenAcceptAsync(Consumer<? super T> action, Executor executor) public CompletableFuture<Void> thenRun(Runnable action) public CompletableFuture<Void> thenRunAsync(Runnable action) public CompletableFuture<Void> thenRunAsync(Runnable action,Executor executor)
5.4 对计算速度进行选用
-
applyToEither
:有两个任务有,获取先执行完的任务的返回值,处理任务并有新的返回值public <U> CompletableFuture<U> applyToEither( CompletionStage<? extends T> other, Function<? super T, U> fn) public <U> CompletableFuture<U> applyToEitherAsync( CompletionStage<? extends T> other, Function<? super T, U> fn) public <U> CompletableFuture<U> applyToEitherAsync( CompletionStage<? extends T> other, Function<? super T, U> fn, Executor executor)
-
acceptEither
:有两个任务有,获取先执行完的任务的返回值,处理任务,没有新的返回值public CompletableFuture<Void> acceptEither( CompletionStage<? extends T> other, Consumer<? super T> action) public CompletableFuture<Void> acceptEitherAsync( CompletionStage<? extends T> other, Consumer<? super T> action) public CompletableFuture<Void> acceptEitherAsync( CompletionStage<? extends T> other, Consumer<? super T> action, Executor executor)
-
runAfterEither
:两个任务有一个执行完成,不需要获取 future 的结果,处理任务,也没有返回值public CompletableFuture<Void> runAfterEither(CompletionStage<?> other,Runnable action) public CompletableFuture<Void> runAfterEitherAsync(CompletionStage<?> other,Runnable action) public CompletableFuture<Void> runAfterEitherAsync(CompletionStage<?> other, Runnable action, Executor executor)
5.5 对计算结果进行合并
-
thenCombine
:两个CompletionStage任务都完成后,最终把两个任务的结果一起交给thenCombine来处理,先完成的先等着,等待其他分支任务ublic <U,V> CompletableFuture<V> thenCombine( CompletionStage<? extends U> other, BiFunction<? super T,? super U,? extends V> fn) public <U,V> CompletableFuture<V> thenCombineAsync( CompletionStage<? extends U> other, BiFunction<? super T,? super U,? extends V> fn) public <U,V> CompletableFuture<V> thenCombineAsync( CompletionStage<? extends U> other, BiFunction<? super T,? super U,? extends V> fn, Executor executor)
-
thenAcceptBoth
:返回一个新的CompletionStage,当这个和另一个给定的阶段都正常完成时,两个结果作为提供的操作的参数被执行public <U> CompletableFuture<Void> thenAcceptBoth( CompletionStage<? extends U> other, BiConsumer<? super T, ? super U> action) public <U> CompletableFuture<Void> thenAcceptBothAsync( CompletionStage<? extends U> other, BiConsumer<? super T, ? super U> action) public <U> CompletableFuture<Void> thenAcceptBothAsync( CompletionStage<? extends U> other, BiConsumer<? super T, ? super U> action, Executor executor)
-
runAfterBoth
:返回一个新的CompletionStage,当这个和另一个给定的阶段都正常完成时,执行给定的动作。public CompletableFuture<Void> runAfterBoth(CompletionStage<?> other, Runnable action) public CompletableFuture<Void> runAfterBothAsync(CompletionStage<?> other, Runnable action) public CompletableFuture<Void> runAfterBothAsync(CompletionStage<?> other, Runnable action, Executor executor)
5.6 多任务组合
-
allOf
:等待所有任务完成public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs) { return andTree(cfs, 0, cfs.length - 1); }
-
anyOf
:只要有一个任务完成public static CompletableFuture<Object> anyOf(CompletableFuture<?>... cfs) { return orTree(cfs, 0, cfs.length - 1); }