CompletableFuture引入
CompletableFuture是java8引入的一个用于异步编程的工具类。
我们根据CompletableFuture的简单示例来逐步介绍它的强大功能。
final CompletableFuture<String> cf = new CompletableFuture<String>();
Thread t = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
System.out.println(cf.get());
System.out.println("thread exit ...");
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(cf.join()); // 抛出未检查异常unchecked exception,即RuntimeException,其它与get差不多,阻塞获取值
}
});
t.setDaemon(true);
t.start();
cf.complete("finish ... ");
上述例子,我们可以发现CompletableFuture它也是一个future,可以通过future.get()来获取到结果。
CompletableFuture还提供了join()
方法,它的功能和get()方法是一样的,都是阻塞获取值,它们的区别在于 join() 抛出的是 unchecked Exception。
t.setDaemon和睡眠时间1秒是为了保证主线程在子线程执行完前结束,后台线程(子线程)自动结束,不打印结果。
下面介绍几个static静态方法,它们使用任务来实例化一个CompletableFuture实例
CompletableFuture.runAsync(Runnable runnable);
CompletableFuture.runAsync(Runnable runnable, Executor executor);
CompletableFuture.supplyAsync(Supplier<U> supplier);
CompletableFuture.supplyAsync(Supplier<U> supplier, Executor executor);
CompletableFuture.runAsync(new Runnable(){
@Override
public void run() {
System.out.println(Thread.currentThread().isDaemon());
System.out.println("runAsync task ...");
}
});
CompletableFuture future = CompletableFuture.supplyAsync(() -> "resultA");
System.out.println(future.get());
runAsync接收Runnable实例,并没有返回值。
supplyAsync接收Supplier,它是有返回值的。
两方法都可以自定义线程池,表示让任务在自定义线程池中执行,不指定的话,在ForkJoinPool.commonPool()中执行。
注意:线程池中线程默认都是Deamon线程,上面runAsync在main主函数执行时不一定能打印出来。
任务顺序执行
我们可以考虑下执行两个任务的情况,首先执行任务A,然后将任务A的结果传递给任务B。
任务之间的执行存在很多种情况:
- 任务A是否有返回值
- 任务B是否需要接收任务A的返回值
- 任务B是否有返回值
不过可以明确的是,肯定是任务A执行完再执行任务B
CompletableFuture.runAsync(() -> {}).thenRun(() -> {});
CompletableFuture.runAsync(() -> {}).thenAccept(resultA -> {});
CompletableFuture.runAsync(() -> {}).thenApply(resultA -> "resultB");
CompletableFuture.supplyAsync(() -> "resultA").thenRun(() -> {});
CompletableFuture.supplyAsync(() -> "resultA").thenAccept(resultA -> {});
CompletableFuture.supplyAsync(() -> "resultA").thenApply(resultA -> resultA + " resultB");
前3行代码演示的是,任务A无返回值,所以对应的第2行和第3行代码中,resultA其实是 null
。
第4行用的是 thenRun(Runnable runnable)
,任务A执行完执行B,并且B不需要 A 的结果。
第5行用的是 thenAccept(Consumer action)
,任务A执行完执行B,B需要A的结果,但是任务B不返回值。
第6行用的是 thenApply(Function fn)
,任务A执行完执行B,B需要A的结果,同时任务B有返回值。
注意:如果任务B后面还有任务C,往下继续调用 .thenXxx()
即可。
异常处理
CompletableFuture的异常处理主要介绍如下两个方法:
public CompletableFuture<T> exceptionally(Function<Throwable, ? extends T> fn);
public <U> CompletionStage<U> handle(BiFunction<? super T, Throwable, ? extends U> fn);
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "resultA")
.thenApply(resultA -> resultA + " resultB")
.thenApply(resultB -> resultB + " resultC")
.thenApply(resultC -> resultC + " resultD");
System.out.println(future.join());
// 打印结果:resultA resultB resultC resultD
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
throw new RuntimeException();
})
.exceptionally(ex -> "errorResultA")
.thenApply(resultA -> resultA + " resultB")
.thenApply(resultB -> resultB + " resultC")
.thenApply(resultC -> resultC + " resultD");
System.out.println(future.join());
// 打印结果:errorResultA resultB resultC resultD
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "resultA")
.thenApply(resultA -> resultA + " resultB")
.thenApply(resultB -> {
// 此处任务C抛出异常
throw new RuntimeException();
})
// 处理任务C的返回值或者异常
.handle(new BiFunction<Object, Throwable, Object>() {
@Override
public Object apply(Object re, Throwable throwable) {
if (throwable != null) {
return "errorResultC";
}
return re;
}
})
.thenApply(resultC -> resultC + " resultD");
System.out.println(future.join());
- 方式1返回结果是正常的。
- 方式2通过exceptionally解决异常问题,并返回新的结果,这个新的结果将传递给任务B。
- 方式3通过handle解决异常问题,handle处理任务C抛出的异常, re和throwable必然有一个是null,分别代表正常的执行结果和异常的情况。注意(re和throwable也有可能都为空,如果任务C没有返回值情况下)。
合并两个任务结果
前面我们讲述了任务的顺序执行,后一个任务执行依赖于前一个任务返回值。
这里讲述任务之间的并行处理,没有先后顺序区分,合并各个任务处理后的结果,以便进行后续操作。
CompletableFuture<String> cfA = CompletableFuture.supplyAsync(() -> "resultA");
CompletableFuture<String> cfB = CompletableFuture.supplyAsync(() -> "resultB");
cfA.thenAcceptBoth(cfB, (resultA, resultB) -> {
System.out.println(resultA + " " + resultB);
});
CompletableFuture<String> cf = cfA.thenCombine(cfB, (resultA, resultB) -> resultA + " " + resultB);
System.out.println(cf.get());
cfA.runAfterBoth(cfB, () -> {
System.out.println("runAfterBoth ... ");
});
- thenAcceptBoth表示后续的处理不会返回值
- thenCombine表示需要返回值
- runAfterBoth表示不需要任务A,B的返回值
取多个任务结果
这里介绍两个非常简单的静态方法:allOf()和anyOf()方法
public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs){...}
public static CompletableFuture<Object> anyOf(CompletableFuture<?>... cfs) {...}
CompletableFuture cfA = CompletableFuture.supplyAsync(() -> "resultA");
CompletableFuture cfB = CompletableFuture.supplyAsync(() -> 123);
CompletableFuture cfC = CompletableFuture.supplyAsync(() -> "resultC");
CompletableFuture<Void> future1 = CompletableFuture.allOf(cfA, cfB, cfC);
future1.join();
CompletableFuture<Object> future2 = CompletableFuture.anyOf(cfA, cfB, cfC);
Object result = future2.join();
System.out.println(result);
- allOf、anyOf区别
- allOf聚合了多个CompletableFuture实例,它没有返回值。future1.join()的调用将阻塞,直到所有任务执行结束。
- anyOf其中任意一个任务完成,future2.join就会返回值,它总是返回最先完成的任务。
either
相比于anyOf, 如果只需要处理两个CompletableFuture实例,可以使用xxxEither方法。
CompletableFuture cfA = CompletableFuture.supplyAsync(() -> "resultA");
CompletableFuture cfB = CompletableFuture.supplyAsync(() -> 123);
// 没有返回值,result随机值(resultA或者123)
cfA.acceptEither(cfB, result -> {
System.out.println(result);
});
cfA.acceptEitherAsync(cfB, result -> {});
cfA.acceptEitherAsync(cfB, result -> {}, executorService);
// 有返回值,返回result随机值(resultA或者123)
CompletableFuture cf = cfA.applyToEither(cfB, result -> {return result;});
System.out.println(cf.get());
cfA.applyToEitherAsync(cfB, result -> {return result;});
cfA.applyToEitherAsync(cfB, result -> {return result;}, executorService);
// 不需要任务A、B的执行结果,没有返回值
cfA.runAfterEither(cfB, () -> {
System.out.println("runAfterEither ... ");
});
cfA.runAfterEitherAsync(cfB, () -> {});
cfA.runAfterEitherAsync(cfB, () -> {}, executorService);
带either的方法,指的是两个任务中的其中一个执行完成,就执行指定的操作。上面几组的区别,在上述代码中已有注释。
几个注意点如下:
cfA.acceptEither(cfB, result -> {}); 和cfB.acceptEither(cfA, result -> {}); 意思一致,只是写法上的区别。
同组各个方法的区别
- 方法一:它由任务A或任务B所在的执行线程来执行,取决于哪个任务先结束,并非同步
- 方法二:带Async后缀方法,代表将需要执行的任务放到 ForkJoinPool.commonPool()中执行
- 方法三:将任务放到指定线程池中执行
compose
前面介绍了thenAcceptBoth
和thenCombine
用于聚合两个任务,其实compose也是一样的功能,它们本质上都是为了让多个 CompletableFuture实例形成一个链。
CompletableFuture<String> cfA = CompletableFuture.supplyAsync(() -> {
System.out.println("processing a...");
return "hello";
});
CompletableFuture<String> cfB = CompletableFuture.supplyAsync(() -> {
System.out.println("processing b...");
return " world";
});
CompletableFuture<String> cfC = CompletableFuture.supplyAsync(() -> {
System.out.println("processing c...");
return ", I'm robot!";
});
CompletableFuture cf = cfA.thenCombine(cfB, (resultA, resultB) -> {
return resultA + resultB;
}).thenCombine(cfC, (resultAB, resultC) -> {
return resultAB + resultC;
});
System.out.println(cf.get()); // hello world, I'm robot
CompletableFuture<String> result = CompletableFuture.supplyAsync(() -> {
// 第一个实例的结果
return "hello";
}).thenCompose(resultA -> CompletableFuture.supplyAsync(() -> {
// 把上一个实例的结果传递到这里
return resultA + " world";
})).thenCompose(resultAB -> CompletableFuture.supplyAsync(() -> {
return resultAB + ", I'm robot";
}));
System.out.println(result.join()); // hello world, I'm robot
thenCombine和thenCompose区别
thenCombine的cfA、cfB、cfC之间完全不存在数据依赖关系,执行顺序随机,只不过是按照指定结果聚合在了一起。
thenCompose后一个实例能获取到前一个实例执行的结果,执行有序。
thenApply和thenCompose的区别
CompletableFuture<String> future1 = CompletableFuture
.supplyAsync(() -> "hello")
.thenApply(cfA -> cfA + " world");
System.out.println(future1.get());
CompletableFuture<String> future2 = CompletableFuture
.supplyAsync(() -> "hello")
.thenCompose(cfA -> CompletableFuture.supplyAsync(() -> cfA + " world"));
System.out.println(future2.get());
两者都接收一个Function
thenApply中返回一个具体值,thenCompose返回一个cf实例
thenApply同步,thenCompose异步