这个CompletableFuture是JDK1.8版本新引入的类 ,每一个新技术或者新特性或者新模式的的出现,要思考它们解决的是什么问题,为什么会有CompletableFuture这个东东?
下面看下个例子:
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(10);
final Future<?> submit = executorService.submit(() -> {
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
while (!submit.isDone()) {
}
System.out.println(" finished");
}
上述代码,Future设计模式的弊端在于我们需要自己去拿结果。而且Future的get()方法是阻塞的,如果提交一批任务,你并不知道哪个是先执行的,哪个是先返回的.虽然是可以通过CompletionService可以解决这样的一个问题。但是Future get()出来的那个结果,紧接着如果你想在提交一个Callback或者一个Runnable你必须从新定义一个,无法达到级联。但是有没有一种方式就是说我不跟你去要结果,我注册一个回调给你,你通知我,可以实现级联效果。那这就是我们这片文章要讲的ExecutorService和Future的结合CompletableFuture。
Future 的局限性
- 不能手动完成 当你写了一个函数,用于通过一个远程API获取一个电子商务产品最新价格。因为这个 API 太耗时,你把它允许在一个独立的线程中,并且从你的函数中返回一个 Future。现在假设这个API服务宕机了,这时你想通过该产品的最新缓存价格手工完成这个Future 。你会发现无法这样做。
- Future 的结果在非阻塞的情况下,不能执行更进一步的操作 Future 不会通知你它已经完成了,它提供了一个阻塞的
get()
方法通知你结果。你无法给 Future 植入一个回调函数,当 Future 结果可用的时候,用该回调函数自动的调用 Future 的结果。 - 多个 Future 不能串联在一起组成链式调用 有时候你需要执行一个长时间运行的计算任务,并且当计算任务完成的时候,你需要把它的计算结果发送给另外一个长时间运行的计算任务等等。你会发现你无法使用 Future 创建这样的一个工作流。
- 不能组合多个 Future 的结果 假设你有10个不同的Future,你想并行的运行,然后在它们运行未完成后运行一些函数。你会发现你也无法使用 Future 这样做。
- 没有异常处理 Future API 没有任务的异常处理结构居然有如此多的限制,幸好我们有CompletableFuture,你可以使用 CompletableFuture 达到以上所有目的。
CompletableFuture 实现了 Future
和 CompletionStage
接口,并且提供了许多关于创建,链式调用和组合多个 Future 的便利方法集,而且有广泛的异常处理支持。
Java 1.8 Lamaba表达式或者java.util.function包下的@FunctionInterface注解的接口相关的知识就不多介绍
runAsync方法
//接收任务,并且是在@link ForkJoinPool#commonPool()}中运行的。最后返回一个新的CompletableFuture
public static CompletableFuture<Void> runAsync(Runnable runnable) {
return asyncRunStage(asyncPool, runnable);
}
thenAccept方法
//接收用户自己实现的一个Consumer的动作,返回一个新的CompletableFuture
public CompletableFuture<Void> thenAccept(Consumer<? super T> action) {
return uniAcceptStage(null, action);
}
whenComplete方法
//接收一个BiConsumer<value,Thrwoable>的动作返回一个新的CompletableFuture
public CompletableFuture<T> whenComplete(
BiConsumer<? super T, ? super Throwable> action) {
return uniWhenCompleteStage(null, action);
}
supplyAsync方法
//接受一个Supplier带有返回值的function类型,返回一个新的CompletableFuture
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier) {
return asyncSupplyStage(asyncPool, supplier);
}
案例一:
public static void main(String[] args) throws InterruptedException {
//一个异步任务
CompletableFuture.runAsync(() -> {
try {
TimeUnit.SECONDS.sleep(5);
System.out.println("process......");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).whenComplete((v, t) -> System.out.println("DONE"));
//Thread.currentThread().join();
}
上述代码是不会执行的,因为它默认创建的是守护线程,main结束它也跟着结束了,因为CompletableFuture不需要关注ExecutorServcie,而是内部自己实现了一个,它代表的仅仅是一个Future。
java.util.concurrent.ForkJoinPool#registerWorker部分代码:
//注册工作线程时设置为守护线程
final WorkQueue registerWorker(ForkJoinWorkerThread wt) {
UncaughtExceptionHandler handler;
wt.setDaemon(true); //设置为守护线程
if ((handler = ueh) != null)
wt.setUncaughtExceptionHandler(handler);
WorkQueue w = new WorkQueue(this, wt);
案例二:
用 Future的方式来做一个实验:
场景:调用一个查询数据库的方法,拿该方法的返回结果做进一步处理;
//模拟读取数据库(有耗时)
private static int readMysql() {
int value = ThreadLocalRandom.current().nextInt(20);
try {
System.out.println(Thread.currentThread().getName() + " -readMysql start.");
TimeUnit.SECONDS.sleep(value);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " -readMysql end. value is:"+value);
return value;
}
//拿到数据库的结果做打印处理 (有耗时)
private static void printData(int data) {
int value = ThreadLocalRandom.current().nextInt(20);
try {
System.out.println(Thread.currentThread().getName() + "-printData start.");
TimeUnit.SECONDS.sleep(value);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "- printData end. " + data);
}
public static void main(String[] args) throw InterruptedException{
//创建一个固定的 线程池大小为10
ExecutorService executorService = Executors.newFixedThreadPool(10);
//执行任务的集合
final List<Callable<Integer>> runTasks = IntStream.range(0, 10).boxed().map(i -> (Callable<Integer>) () -> readMysql()).collect(Collectors.toList());
final List<Future<Integer>> futureList = executorService.invokeAll(runTasks);
futureList.stream().map(futures -> {
try {
return futures.get(); // 拿到数据库的值 该方法是阻塞的
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
return null;
//作进一步处理,将拿到的数据库的值打印出来
}).parallel().forEach(CompletableFutureExp::printData);
}
执行结果如下:
pool-1-thread-1 -readMysql start.
pool-1-thread-2 -readMysql start.
pool-1-thread-3 -readMysql start.
pool-1-thread-4 -readMysql start.
pool-1-thread-5 -readMysql start.
pool-1-thread-6 -readMysql start.
pool-1-thread-7 -readMysql start.
pool-1-thread-7 -readMysql end. value is :0
pool-1-thread-8 -readMysql start.
pool-1-thread-10 -readMysql start.
pool-1-thread-9 -readMysql start.
pool-1-thread-4 -readMysql end. value is :1
pool-1-thread-5 -readMysql end. value is :3
pool-1-thread-6 -readMysql end. value is :3
pool-1-thread-10 -readMysql end. value is :5
pool-1-thread-9 -readMysql end. value is :7
pool-1-thread-3 -readMysql end. value is :10
pool-1-thread-8 -readMysql end. value is :13
pool-1-thread-1 -readMysql end. value is :17
pool-1-thread-2 -readMysql end. value is :18
main-printData start.
ForkJoinPool.commonPool-worker-2-printData start.
ForkJoinPool.commonPool-worker-1-printData start.
ForkJoinPool.commonPool-worker-2-printData end. 7
ForkJoinPool.commonPool-worker-1-printData end. 10
ForkJoinPool.commonPool-worker-2-printData start.
ForkJoinPool.commonPool-worker-1-printData start.
ForkJoinPool.commonPool-worker-3-printData start.
ForkJoinPool.commonPool-worker-1-printData end. 3
ForkJoinPool.commonPool-worker-1-printData start.
ForkJoinPool.commonPool-worker-2-printData end. 5
ForkJoinPool.commonPool-worker-2-printData start.
ForkJoinPool.commonPool-worker-1-printData end. 1
ForkJoinPool.commonPool-worker-1-printData start.
ForkJoinPool.commonPool-worker-2-printData end. 13
ForkJoinPool.commonPool-worker-2-printData start.
main-printData end. 0
ForkJoinPool.commonPool-worker-1-printData end. 3
ForkJoinPool.commonPool-worker-2-printData end. 17
ForkJoinPool.commonPool-worker-3-printData end. 18
可以明确的看到,在打印值得时候并不是某一个线程拿到数据库的值立即去执行printData方法,而是要等到所有线程拿到数据库的值才去进行下一步printData操作....这当然不是我们想要的。
案例三:
用 CompletableFuture的方式来做一个实验:
public static void main(String[] args) throw InterruptedException{
IntStream.range(0, 10)
.boxed()
.forEach(i -> CompletableFuture.supplyAsync(CompletableFutureExp::readMysql)
.thenAccept(CompletableFutureExp::printData)
.whenComplete((v, t) -> System.out.println(i + " finished.")));
Thread.currentThread().join();
}
执行结果如下:
ForkJoinPool.commonPool-worker-1 -readMysql start.
ForkJoinPool.commonPool-worker-2 -readMysql start.
ForkJoinPool.commonPool-worker-3 -readMysql start.
ForkJoinPool.commonPool-worker-2 -readMysql end. value is :1
ForkJoinPool.commonPool-worker-2-printData start.
ForkJoinPool.commonPool-worker-3 -readMysql end. value is :8
ForkJoinPool.commonPool-worker-3-printData start.
ForkJoinPool.commonPool-worker-1 -readMysql end. value is :10
ForkJoinPool.commonPool-worker-1-printData start.
ForkJoinPool.commonPool-worker-2-printData end. 1
1 finished.
ForkJoinPool.commonPool-worker-2 -readMysql start.
ForkJoinPool.commonPool-worker-1-printData end. 10
0 finished.
ForkJoinPool.commonPool-worker-1 -readMysql start.
ForkJoinPool.commonPool-worker-3-printData end. 8
2 finished.
ForkJoinPool.commonPool-worker-3 -readMysql start.
ForkJoinPool.commonPool-worker-1 -readMysql end. value is :5
ForkJoinPool.commonPool-worker-1-printData start.
ForkJoinPool.commonPool-worker-2 -readMysql end. value is :17
ForkJoinPool.commonPool-worker-2-printData start.
ForkJoinPool.commonPool-worker-3 -readMysql end. value is :19
ForkJoinPool.commonPool-worker-3-printData start.
ForkJoinPool.commonPool-worker-1-printData end. 5
4 finished.
ForkJoinPool.commonPool-worker-1 -readMysql start.
ForkJoinPool.commonPool-worker-1 -readMysql end. value is :1
ForkJoinPool.commonPool-worker-1-printData start.
ForkJoinPool.commonPool-worker-3-printData end. 19
5 finished.
ForkJoinPool.commonPool-worker-3 -readMysql start.
ForkJoinPool.commonPool-worker-2-printData end. 17
3 finished.
ForkJoinPool.commonPool-worker-2 -readMysql start.
ForkJoinPool.commonPool-worker-1-printData end. 1
6 finished.
ForkJoinPool.commonPool-worker-1 -readMysql start.
ForkJoinPool.commonPool-worker-3 -readMysql end. value is :14
ForkJoinPool.commonPool-worker-3-printData start.
ForkJoinPool.commonPool-worker-1 -readMysql end. value is :14
ForkJoinPool.commonPool-worker-1-printData start.
ForkJoinPool.commonPool-worker-3-printData end. 14
7 finished.
ForkJoinPool.commonPool-worker-2 -readMysql end. value is :18
ForkJoinPool.commonPool-worker-2-printData start.
ForkJoinPool.commonPool-worker-1-printData end. 14
9 finished.
可以看到,和案例二的结果不同的是,某一个线程readMysql之后,会立即去printData,这正是我们期望的。
作者实力有限,若有问题,请指出,如有雷同,请联系作者,及时删除,写作不易,未经许可,转载请注明出处,如对您工作有帮助,烦劳高台贵手,请点赞+收藏+关注,未完待续.......