同步异步与CompletableFuture
同步异步概念
同步方法必须等方法调用完才能返回结果,类似与一条线;异步方法像是开启一个通知,通知后立即返回。
Future
对于异步任务,线程任务中,如果有返回值,如果我们不是很急着需要获取返回结果,那么需要用到Future。当需要用到任务结果的时候,可以调用future.get 获取放回值。
线程池

备注:上图截取于《java 高并发程序设计》一书,部分方法省略。 教详细的Excutorservice 在jdk11中有如下方法:
public interface ExecutorService extends Executor { void shutdown(); List<Runnable> shutdownNow(); boolean isShutdown(); boolean isTerminated(); boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException; <T> Future<T> submit(Callable<T> task); <T> Future<T> submit(Runnable task, T result); Future<?> submit(Runnable task); <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException; <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException; <T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException; <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException; }
我们常用的线程池Excutorservice接口,其中有三个重载submit方法可以返回future。
这里如果需要获取任务的返回值,那么参数传递应该是submit(Callable task),即传递一个具有返回值的任务。
CompletableFuture
CompletableFuture是future的一种拓展,具备future的所有特性,它提供了更强大的功能,适合链式编程。
主要提供两类工厂方法:supplyAsync()、 runAsync()。其中supplyAsync用于获取有放回置的场景,runAsync用于获取没有返回值的场景。
另外,这两类方法均支持指定线程池,如果不指定,则默认使用系统的ForkJoinPool线程池。ForkJoinPool会根据系统cpu核数来构建不同的线程池。
待补充点1:ForkJoinPool底层构建不同线程池逻辑。
代码示例 (下列示例均不指定特定线程池)——
supplyAsync()
具备返回值的
@Test
public void test1() {
//具有返回值的
CompletableFuture<Boolean> booleanCompletableFuture = CompletableFuture.supplyAsync(() -> {
//业务代码返回
return true;
});
}
集合返回值并且阻塞
@Test
public List<CompletableFuture<Boolean>> test11() {
//集合的返回 异步并且阻塞
List<CompletableFuture<Boolean>> futureList = list.stream().map(class1 -> CompletableFuture.supplyAsync(() -> {
//每个对象分别返回业务代码
return true;
})).collect(Collectors.toList());
futureList.forEach(CompletableFuture::join);
return futureList;
}
runAsync()
无返回值
@Test
public void test2() {
//无返回值的
CompletableFuture<Void> voidCompletableFuture = CompletableFuture.runAsync(() -> {
//业务代码无返回值
});
}
@Test
public void test22() {
//无返回值 阻塞
CompletableFuture<Void> voidCompletableFuture = CompletableFuture.runAsync(() -> {
//业务代码无返回值
});
voidCompletableFuture.join();
//voidCompletableFuture.get(); get 的需要try catch 捕获异常
}
————————————————————————————————————————————20210119
流式调用和通知
公用判断方法
public class JudgeIsDouble {
public static int judgeDouble(int num) {
if (num%2 == 0) {
return 1;
}
return 0;
}
}
-
流式调用之并行
流式调用,配合并行可增加效率
@Test
public void test11() {
/*IntStream range = IntStream.range(1, 1000000);
range.mapToObj(JudgeIsDouble::judgeDouble).collect(Collectors.toList());*/
for (int i = 0; i < 100000; i++) {
list.add(i);
}
//集合的返回 异步 非并行
long time1 = System.currentTimeMillis();
list.stream().map(obj -> CompletableFuture.supplyAsync(() -> {
//业务代码 每个对象分别返回
return JudgeIsDouble.judgeDouble(obj);
}));
long time2 = System.currentTimeMillis();
System.out.println(time2-time1);
//集合的返回 异步 并行
long time11 = System.currentTimeMillis();
list.stream().parallel().map(obj -> CompletableFuture.supplyAsync(() -> {
//业务代码 每个对象分别返回
return JudgeIsDouble.judgeDouble(obj);
}));
long time22 = System.currentTimeMillis();
System.out.println(time22-time11);
}
//重复多次 第二种均比第一种时间少
294
158
- CompletableFuture 流式调用
@Test
public void test4() {
for (int i = 0; i < 100; i++) {
list.add(i);
}
Stream<CompletableFuture<Void>> completableFutureStream = list.stream().parallel().map(obj -> CompletableFuture.supplyAsync(() -> {
//业务代码 每个对象分别返回
return JudgeIsDouble.judgeDouble(obj);
}).thenApply(i -> Integer.toString(i))
.thenApply((res) -> "\"" + res + "\"")
.thenAccept(System.out::println));
//等待完成 不加这个上面代码式异步的,直接输出了
completableFutureStream.forEach(CompletableFuture::join);
}
注意,completableFutureStream最后必须阻塞后才能,否则主线程无法获取异步线程计算的返回值。另外,
get() 方法会抛出经检查的异常,可被捕获,自定义处理或者直接抛出,而join会抛出未经检查的异常。
-
CompletableFuture 异常
public class JudgeIsDouble { public static int judgeDouble(int num) { if (num == 6) { throw new IndexOutOfBoundsException("error ! num can not equal 6"); } if (num%2 == 0) { return 1; } return 0; } }可以配合流式调用一起,加上exceptionally()方法抛出异常。
@Test
public void test5() {
for (int i = 0; i < 100; i++) {
list.add(i);
}
Stream<CompletableFuture<Integer>> completableFutureStream = list.stream().parallel().map(obj -> CompletableFuture.supplyAsync(() -> {
return JudgeIsDouble.judgeDouble(obj);
}).exceptionally(ex -> {
System.out.println(ex.toString());
return 2;
}));
//等待完成 不加这个上面代码式异步的,直接输出了
completableFutureStream.forEach(obj -> {
try {
obj.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
});
}
—————————————————————————————————— 结果
java.util.concurrent.CompletionException: java.lang.IndexOutOfBoundsException: error ! num can not equal 6
-
CompletableFuture 完成通知 (待)
-
多个异步任务处理
假设有一种场景: 你去餐馆点菜,你去之后一定是按照菜单点餐,点餐后厨师会炒菜,服务员1准备餐具,服务员2准备饮料,服务员3在你们厨师炒菜好了以及前面的服务员准备好餐具和饮品后再上饭,然后你才吃到饭菜。一共四个任务,有并行处理的,有顺序处理的。
@Test
public void test6() throws ExecutionException, InterruptedException {
CompletableFuture<String> stringCompletableFuture = CompletableFuture.supplyAsync(() -> {
int num = 5;
System.out.printf("第一步:客户点餐, 点了 %d 个菜", num);
System.out.println("\n");
return num;
//点菜后,这个任务的结果:点菜数目,传递到下一个CompletableFuture,利用thenCompose
}).thenCompose((num) -> {
//点菜是阶段1, 厨师做菜 和服务员准备餐具 是阶段2,阶段2的任务没有现后关系
return CompletableFuture.supplyAsync(() -> {
System.out.println("第二步:厨师开始做菜!");
for (int i = 1; i <= num; i++) {
System.out.printf("正在准备第%d个菜", i);
System.out.print("\n");
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return "厨师准备完成\n";
//厨师做菜和服务员准备餐具 不相关的两个并行任务, 但是需要两个都准备好才行
}).thenCombine(CompletableFuture.supplyAsync(() -> {
System.out.println("第二步:服务员1正在准备餐具!");
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "服务员1准备完成了\n";
}), (i, j) -> (i + j)).thenCombine(CompletableFuture.supplyAsync(() -> {
System.out.println("第二步:服务员2正在准备饮品");
try {
sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "服务员2准备完成了\n";
}), (str1, str2) -> "第二步完成!");
}).thenCompose((str) -> CompletableFuture.supplyAsync(() -> {
System.out.println(str);
System.out.println("第三步:菜和餐具准备完成,服务员3开始准备饭!");
try {
sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "服务员3盛饭完成,客户准备吃饭";
})).thenApply((str) -> {
System.out.println(str);
String res = "客人开始吃饭";
return res;
});
//阻塞查看结果
String s = stringCompletableFuture.join();
//String s = stringCompletableFuture.get();
System.out.println(s);
}
结果:
F:\program\jdk-11.0.8+10\bin\java.exe -ea -Didea.test.cyclic.buffer.size=1048576 "TestCompletableFuture,test6
第一步:客户点餐, 点了 5 个菜
第二步:厨师开始做菜!
正在准备第1个菜
第二步:服务员1正在准备餐具!
第二步:服务员2正在准备饮品
正在准备第2个菜
正在准备第3个菜
正在准备第4个菜
正在准备第5个菜
第二步完成!
第三步:菜和餐具准备完成,服务员3开始准备饭!
服务员3盛饭完成,客户准备吃饭
客人开始吃饭
Process finished with exit code 0
上面案例中,可以分为这么几个阶段:第一阶段,客户点餐,第二阶段:厨师开始炒菜,服务员1和服务员2准备餐具和饮品;第三阶段:厨师炒好菜了,服务员1和2也准备好东西了,服务员3开始盛饭。服务员3盛饭完成后,最后客人开始吃饭。
需要注意几点:
thenCompose 可以用于组合多个CompletableFuture,将前一个任务的返回结果作为下一个completionStage,它们之间存在着业务逻辑上的先后顺序。thenCompose 和thenApply方法的区别是:thenApply还是原来的CompletableFuture,而thenCompose 用以连接新的CompletableFuture,它返回的是一个CompletableFuture对象。
如果某个场景需要用到两个异步任务的结果,可以使用thenCombine,它是用以连接两个任务的一种方法。
方法签名是:
public <U,V> CompletableFuture<V> thenCombine(
CompletionStage<? extends U> other,
BiFunction<? super T,? super U,? extends V> fn) {
return biApplyStage(null, other, fn);
}
它会完成在调用任务和other异步任务后,将结果一起传递给BiFunction,BiFunction是是一种接受两个参数有一个返回值的接口,执行结束后最终返回的是一个CompletableFuture对象!
本文深入讲解了Java中的同步与异步编程概念,并重点介绍了Future、线程池及CompletableFuture的应用。通过实例演示了CompletableFuture的强大功能,包括流式调用、异常处理、任务组合等。
3426

被折叠的 条评论
为什么被折叠?



