普通情况下,我们的接口逻辑都是串行化的,有时候在我们方法中可能存在着非常耗时的操作这样就会造成代码阻塞,但是呢,为了用户的体验,我们可能需要将一些复杂的数据开启线程进行异步处理。
所谓异步,其实就是实现一个可无需等待被调用函数的返回值而让操作继续运行的方法,简单的讲就是另启一个线程来完成调用中的部分计算,使调用继续运行或返回,而不需要等待计算结果。
Java8 提供的CompletableFuture
可以自定义线程池或使用默认线程池对数据进行异步处理,且可以根据需求选择是否返回异步结果!灵活的使用CompletableFuture
可以让我们感受java8的异步编程之美!
以下,便是CompletableFuture
的异步操作创建方式示例。
CompletableFuture的静态工厂方法
方法名 | 描述 |
---|---|
runAsync(Runnable runnable) | 使用ForkJoinPool.commonPool()作为它的线程池执行异步代码。此方法无法返回值 |
runAsync(Runnable runnable, Executor executor) | 使用指定的thread pool执行异步代码。此方法无法返回值。 |
supplyAsync(Supplier supplier) | 使用ForkJoinPool.commonPool()作为它的线程池执行异步代码,异步操作有返回值 |
supplyAsync(Supplier supplier, Executor executor) | 使用指定的thread pool执行异步代码,异步操作有返回值 |
CompletableFuture
的思想是,当被调用时,它们会立即被安排启动开始执行异步任务(与流式操作中的延迟计算有着明显区别)。
使用ForkJoinPool.commonPool()
作为它的线程池执行异步代码,
public class AsyncDemo01 {
public static void main(String[] args) {
//当前调用者线程为:main
System.out.println("当前调用者线程为:" + Thread.currentThread().getName());
CompletableFuture.runAsync(() -> {
// 异步方法内当前执行线程为:ForkJoinPool.commonPool-worker-1
System.out.println("异步方法内当前执行线程为:" + Thread.currentThread().getName());
System.out.println(111);
});
}
}
使用指定的线程池执行异步代码。此异步方法无法返回值。
public class AsyncDemo02 {
public static void main(String[] args) {
//当前调用者线程为:main
System.out.println("当前调用者线程为:" + Thread.currentThread().getName());
// fixme 根据阿里规约 建议真实开发时使用 ThreadPoolExecutor 定义线程池
ExecutorService threadPool = Executors.newFixedThreadPool(10);
CompletableFuture.runAsync(() -> {
// 异步方法内当前执行线程为:pool-1-thread-1
System.out.println("异步方法内当前执行线程为:" + Thread.currentThread().getName());
System.out.println(111);
}, threadPool);
// 演示代码,所以选择执行完后关闭线程池
threadPool.shutdown();
}
}
使用ForkJoinPool.commonPool()
作为它的线程池执行异步代码,异步操作有返回值
public class SupplyDemo01 {
public static void main(String[] args) {
CompletableFuture<String> supplyAsync = CompletableFuture.supplyAsync(() -> {
// 异步方法内当前执行线程为:ForkJoinPool.commonPool-worker-1
System.out.println("异步方法内当前执行线程为:" + Thread.currentThread().getName());
// 模拟返回值
return "hello,world";
});
// 获取异步线程执行结果
System.out.println(supplyAsync.join());
}
}
使用指定线程池 来执行可获取返回值的异步任务
public class SupplyDemo02 {
public static void main(String[] args) {
// fixme 根据阿里规约 建议真实开发时使用 ThreadPoolExecutor 定义线程池
ExecutorService threadPool = Executors.newFixedThreadPool(10);
CompletableFuture<String> supplyAsync = CompletableFuture.supplyAsync(() -> {
// 异步方法内当前执行线程为:pool-1-thread-1
System.out.println("异步方法内当前执行线程为:" + Thread.currentThread().getName());
// 模拟耗时与返回结果
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "hello,world";
},threadPool);
// 获取异步线程执行结果
System.out.println(supplyAsync.join());
}
}
CompletableFuture
使用 supplyAsync
来执行异步任务的话,可通过调用 get 或 join方法便可获取异步线程的执行结果。
不同:get方法返回结果,抛出的是检查异常,必须用户throw或者try/catch处理,join返回结果,抛出未检查异常。
相同:join和get方法都是依赖于完成信号并返回结果T的阻塞方法。(阻塞调用者线程,等待异步线程返回结果)。
join:
get:
需要注意的是, completableFuture 的get 方法 有重载,还有一个可传入获取结果等待时间的get方法
如果超过等待时间,异步线程还未返回结果,那么get 调用者线程则会抛出TimeoutException
异常
java8 集合 parallelStream 的出现,让我们多线程开发便捷了不少。使用 parallelStream 可以轻易的将我们的串行化集合元素处理转变为多线程处理,免收代码串行化与阻塞之苦,进而提升我们的接口执行响应速度。
并行流的底层是采用 ForkJoin commonPool线程池来实现的。
在我们的java代码内部实现中,很多地方默认线程池 都是使用的Fork Join 比如:parallelStream
、CompletableFuture
;也就是说,由于多处在使用共同的线程池ForkJoin,所以呢,我们普通的并行流或者CompletableFuture
执行效率可能会受其他地方的影响。
ForkJoin CommonPool,它默认的线程数量就是你的处理器数量 -1 (至少为1),这个值是由Runtime.getRuntime().availableProcessors()得到的。
但是可以通过系统属性java.util.concurrent.ForkJoinPool.common. parallelism来改变线程池大小,如下所示:
System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism","12");
这是一个全局设置,因此它将影响代码中使用ForkJoin线程池的方法
接下来,我们使用一个案例,来测试 并行流parallelStream 与 CompletableFuture 的效率
本次测试机器核心数为:6核
我们定义一些商品名列表,这里就定义和cpu个数相同的商品数,然后模拟通过商品名获取商品详情
模拟任务数等于CPU核心数
// 模拟商品名列表
List<String> proNameList = Arrays.asList("pp", "dd", "cc", "ee", "xx", "ff");
模拟的获取商品详情方法 假设每次查询需耗时 1s
public static String getProduct(String name) {
// 假设此方法 需要远程调用。且有一点耗时
try {
Thread.sleep(1000);
return name.toUpperCase();
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
普通stream 耗时 测试 串行化 一个一个执行
// 普通stream 耗时 测试 串行化 一个一个执行
long start = System.currentTimeMillis();
List<String> collect = proNameList.stream().map(SupplyAsyncDemo01::getProduct)
.filter(Objects::nonNull)
.collect(Collectors.toList());
System.out.println(collect);
long end = System.currentTimeMillis();
System.out.println("stream: " + (end - start));
执行结果:stream: 6078
由于 每单次查询需要耗时1000ms左右,6个产品查询6136ms 属于正常
long startParallel = System.currentTimeMillis();
List<String> collectParallel = proNameList.parallelStream()
.map(x -> {
String product = getProduct(x);
System.out.println("parallel-"+Thread.currentThread().getName());
return product;
})
.filter(Objects::nonNull)
.collect(Collectors.toList());
System.out.println(collectParallel);
long endParallel = System.currentTimeMillis();
System.out.println("parallelStream 耗时:" + (endParallel - startParallel));
线程打印:
parallel-ForkJoinPool.commonPool-worker-5
parallel-ForkJoinPool.commonPool-worker-2
parallel-ForkJoinPool.commonPool-worker-3
parallel-ForkJoinPool.commonPool-worker-1
parallel-main
parallel-ForkJoinPool.commonPool-worker-4
执行结果:parallelStream: 1015
我们发现,使用并行流时,虽然核心线程数与上方所说的 cpu-1对上了,但执行过程中,还出现了main线程!六个任务同时交由了六个线程执行,故最终耗时1s
注意点:parallelStream在fork-join commonPool线程池中线程全部使用时,如果仍有任务会使用调用线程临时接替执行!!!
long startCompletable = System.currentTimeMillis();
// 执行异步任务
List<CompletableFuture<String>> completableFutureList = proNameList.stream()
.map(e -> CompletableFuture.supplyAsync(() -> {
String product = getProduct(e);
System.out.println("CompletableFuture-" + Thread.currentThread().getName());
return product;
})).collect(Collectors.toList());
// 获取异步任务结果
List<String> strings = completableFutureList.stream()
.map(CompletableFuture::join)
.filter(Objects::nonNull)
.collect(Collectors.toList());
System.out.println(strings);
long endCompletable = System.currentTimeMillis();
System.out.println("completableFuture 耗时:" + (endCompletable - startCompletable));
线程打印:
CompletableFuture-ForkJoinPool.commonPool-worker-1
CompletableFuture-ForkJoinPool.commonPool-worker-5
CompletableFuture-ForkJoinPool.commonPool-worker-4
CompletableFuture-ForkJoinPool.commonPool-worker-2
CompletableFuture-ForkJoinPool.commonPool-worker-3
CompletableFuture-ForkJoinPool.commonPool-worker-1
执行结果:completableFuture: 2013
什么?任务数等于CPU核心数的情况下 同一个fork-join-commPool线程池 completableFuture 比 parallerStream少了一个调用者线程!故此效率还低于了parallerStream!!!
我们再模拟任务数大于CPU核心数的情况
List<String> proNameList = Arrays.asList("pp", "dd", "cc", "ee", "xx", "ff","gg");
parallelStream:
parallel-ForkJoinPool.commonPool-worker-5
parallel-main
parallel-ForkJoinPool.commonPool-worker-1
parallel-ForkJoinPool.commonPool-worker-2
parallel-ForkJoinPool.commonPool-worker-4
parallel-ForkJoinPool.commonPool-worker-3
parallel-ForkJoinPool.commonPool-worker-5
[PP, DD, CC, EE, XX, FF, GG]
parallelStream 耗时:2019
completableFuture:
CompletableFuture-ForkJoinPool.commonPool-worker-4
CompletableFuture-ForkJoinPool.commonPool-worker-1
CompletableFuture-ForkJoinPool.commonPool-worker-2
CompletableFuture-ForkJoinPool.commonPool-worker-3
CompletableFuture-ForkJoinPool.commonPool-worker-5
CompletableFuture-ForkJoinPool.commonPool-worker-2
CompletableFuture-ForkJoinPool.commonPool-worker-1
[PP, DD, CC, EE, XX, FF, GG]
completableFuture 耗时:2049
这个结果让人相当失望,不是吗?使用CompletableFuture 比使用parallelStream多了这么多代码,但是执行效率是差不多甚至是不如的…
…
…
…
不要急,我们先来扫描战场 复盘一下
当前机器CPU核心数为6个,则fork-join-common-pool 线程数为 (cpu-1)6-1=5个
示例任务数量为 cpu核心数(6)时
并行流:使用了fork-join-common-pool 线程池中的五个线程+调用者线程同时执行,6个任务齐开,故此耗时需要1秒+
completableFuture: 使用了fork-join-common-pool 线程池中的五个线程,但默认不会使用调用者线程,故此 6个任务 被分成了两批(第一批由 common-pool中的五个线程执行 5个,第二批由第一批结束的某个空闲线程执行1个)故此需要耗时2秒+
–
示例任务数量为 cpu核心数+1(7)时
并行流:使用了fork-join-common-pool 线程池中的五个线程+调用者线程同时执行,但因为我们有7个任务,执行会被分为两批,(第一批由 common-pool中的五个线程与调用线程执行6个,第二批由第一批结束的某个空闲线程执行1个)由故此耗时需要2秒+
completableFuture: 我们有7个任务,故此因为任务会被分为两批执行,第一批使用了fork-join-common-pool 线程池中的五个线程执行5个,第二批由第一批结束的某两个空闲线程执行2个)故此需要耗时2秒+
那么问题来了,CompletableFuture 就是鸡肋吗?不 并不是
CompletableFuture仍具有一定的优势,因为它允许你对执行器(Executor)进行配置,尤其是线程池的大小,让它以更适合应用需求的方式进行配置,满足程序的要求,而这是并行流API无法提供的!这样可以打破ForkJoin线程池数量限制,以及避免受其他地方余使用ForkJoin的影响
接下来,我们使用自定义线程池试一试
// stream + 使用CompletableFuture实现 CompletableFuture与并行相比的优势是我们可以自定义线程池
long startCompletableAndThreadPool = System.currentTimeMillis();
// fixme 根据阿里规约 建议真实开发时使用 ThreadPoolExecutor 定义线程池
ExecutorService threadPool = Executors.newFixedThreadPool(proNameList.size());
// 执行异步任务 使用自定义线程池
List<CompletableFuture<String>> futures = proNameList.stream()
.map(e -> CompletableFuture.supplyAsync(() -> getProduct(e), threadPool))
.collect(Collectors.toList());
// 获取异步任务结果
List<String> stringList = futures.stream()
.map(CompletableFuture::join)
.filter(Objects::nonNull)
.collect(Collectors.toList());
System.out.println(stringList);
long endCompletableAndThreadPool = System.currentTimeMillis();
System.out.println("completableFuture-threadPool: " + (endCompletableAndThreadPool - startCompletableAndThreadPool));
执行结果:completableFuture-threadPool: 1022
可以看出,差距明显,由于我们定义了产品个数个线程池执行CompletableFuture ,因此 可以同时并行的一次性请求完所有产品,估计共耗时1000ms左右
再次测试论证,我们将产品扩充到13个 (核心数两倍还多一)
// 模拟商品名列表
List<String> proNameList = Arrays.asList("pp", "dd", "cc", "ee", "xx", "ff", "max", "dd", "qq",
"pp", "dd", "cc", "ee");
结果预测:
stream:
1000ms *13 预计需要耗时 13000ms左右
parallelStream:
由于机器是6核 故此任务被拆分为
(处理6个(5个common-pool线程+1调用者线程):1000ms* 1)
(处理6个(5个common-pool线程+1调用者线程):1000ms* 1)
(处理1个:(common-pool线程或 调用者线程)1000ms *1 )
预计3000ms左右
completableFuture:
由于机器是6核 故此任务被拆分为
(处理5个(5个common-pool线程):1000ms* 1)
(处理5个(5个common-pool线程):1000ms *1 )
(处理3个(3个common-pool线程):1000ms *1 )
预计3000ms左右
completableFuture-threadPool:
由于我上边定义了线程池大小为产品大小,因此 1000ms * 1 预计共耗时 1000ms左右
程序运行结果:
(1)如果你进行的是计算密集型的操作,并且没有I/O,那么推荐使用Stream接口,因为实现简单,同时效率也可能是最高的(如果所有的线程都是计算密集型的,那就没有必要创建比处理器核数更多的线程)。
(2)反之,如果你并行的工作单元还涉及等待I/O的操作(包括网络连接等待),那么使用CompletableFuture灵活性更好,你可以像前文讨论的那样,依据等待/计算,或者W/C的比率设定需要使用的线程数。这种情况不使用并行流的另一个原因是,处理流的流水线中如果发生I/O等待,流的延迟特性会让我们很难判断到底什么时候触发了等待。
当我们第二个任务依赖第一个任务的结果的时候,可以使用 thenApply相关方法来把这两个线程串行化,参数是一个Function(代表着我们需要传入一个转换的函数 具体可参考JAVA8 函数式接口)
thenApply 只可以执行正常的任务,任务出现异常则不会执行 thenApply 方法;如果当第一个任务出现异常时仍要执行第二个任务,可以使用下方的Handle方法
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)
T:上一个异步任务返回值
U: 当前执行最后返回值
thenApply:thenApply中的线程为调用者线程,与CompletableFuture 底层默认所用的 ForkJoin无关!
示例:
public class SupplyDemo03 {
public static void main(String[] args) {
CompletableFuture<String> future = CompletableFuture
.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName());
return "hello";
}).thenApply(e -> {
System.out.println(Thread.currentThread().getName());
return e + ",";
}).thenApply(e -> {
System.out.println(Thread.currentThread().getName());
return (e + "world").toUpperCase();
});
System.out.println(future.join());
}
}
程序执行情况:
最后JOIN 获取结果为 HELLO,WORLD
三个线程分别是 ForkJoinPool.commonPool-worker-1,main,main
thenApplyAsync:当我们第二个任务依赖第一个任务的结果的时候,且第二个任务也想采用异步的方式,则可以使用 thenApplyAsync(Function)
public static void main(String[] args) {
CompletableFuture<String> future = CompletableFuture
.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName());
return "hello";
}).thenApplyAsync(e -> {
System.out.println(Thread.currentThread().getName());
return e + ",";
}).thenApplyAsync(e -> {
System.out.println(Thread.currentThread().getName());
return (e + "world").toUpperCase();
});
System.out.println(future.join());
}
程序执行情况:
最后JOIN 获取结果为 HELLO,WORLD
三个线程均是 是 ForkJoinPool.commonPool-worker-xxxx
当然,我们也可以使用 thenApplyAsync(Function, Executor)
来使用自定义线程池执行我们的异步任务
handle 是执行任务完成时对结果的处理, handle 方法和 thenApply 方法处理方式大致一样,不同的是 handle 是在任务完成后再执行且Handle可以根据可以根据任务是否有异常来进行做相应的后续处理操作。
public <U> CompletionStage<U> handle(BiFunction<? super T, Throwable, ? extends U> fn);
public <U> CompletionStage<U> handleAsync(BiFunction<? super T, Throwable, ? extends U> fn);
public <U> CompletionStage<U> handleAsync(BiFunction<? super T, Throwable, ? extends U> fn,Executor executor);
使用Handler方法 预估异常情况 进行逻辑处理 默认handle中使用的线程为调用者线程
public class HandleDemo01 {
public static void main(String[] args) {
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
System.out.println("当前supplyAsync 执行线程:" + Thread.currentThread().getName());
// 模拟异常
int a = 1 / 0;
return "hello";
}).handle((x, t) -> {
System.out.println("当前handle 执行线程:" + Thread.currentThread().getName());
if (t != null) {
// 出现异常 打印异常信息 或者doSomething
System.out.println("发现上一个异步任务出异常了" + t.getMessage());
} else {
// 未出异常 doSomething
return x;
}
// 设置默认结果
return "error";
});
System.out.println(future.join());
}
}
程序执行情况:
当前supplyAsync 执行线程:ForkJoinPool.commonPool-worker-1
当前handle 执行线程:main
发现上一个异步任务出异常了java.lang.ArithmeticException: / by zero
error
使用自定义的线程 (或者使用默认的ForkJoin)进行异步处理第二个线程的任务
public class HandleDemo01 {
public static void main(String[] args) {
ExecutorService threadPool = Executors.newFixedThreadPool(2);
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
System.out.println("当前supplyAsync 执行线程:" + Thread.currentThread().getName());
// 模拟异常
int a = 1 / 0;
return "hello";
},threadPool).handleAsync((x, t) -> {
System.out.println("当前handle 执行线程:" + Thread.currentThread().getName());
if (t != null) {
// 出现异常 打印异常信息 或者doSomething
System.out.println("发现上一个异步任务出异常了" + t.getMessage());
} else {
// 未出异常 doSomething
return x;
}
// 设置默认结果
return "error";
},threadPool);
System.out.println(future.join());
}
}
程序执行情况:
当前supplyAsync 执行线程:pool-1-thread-1
当前handle 执行线程:pool-1-thread-2
发现上一个异步任务出异常了java.lang.ArithmeticException: / by zero
error
thenCombine 会在两个CompletableFuture任务都执行完成后,把两个任务的结果一块处理。
public <U,V> CompletionStage<V> thenCombine(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn);
public <U,V> CompletionStage<V> thenCombineAsync(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn);
public <U,V> CompletionStage<V> thenCombineAsync(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn,Executor executor);
thenCombine 会在两个CompletableFuture任务都执行完成后,调用者线程会把两个异步任务的结果一块处理
public class ThenCombineDemo01 {
public static void main(String[] args) {
CompletableFuture<String> helloAsync = CompletableFuture.supplyAsync(() -> {
System.out.println("hello 执行线程:" + Thread.currentThread().getName());
return "hello";
});
CompletableFuture<String> worldAsync = CompletableFuture.supplyAsync(() -> {
System.out.println("world 执行线程:" + Thread.currentThread().getName());
return "world";
});
CompletableFuture<String> result = worldAsync.thenCombine(helloAsync, (hello, world) -> {
System.out.println("result 执行线程:" + Thread.currentThread().getName());
return (hello + "," + world).toUpperCase();
});
System.out.println("获取结果 执行线程:" + Thread.currentThread().getName());
System.out.println("两个异步任务合并结果:" + result.join());
}
}
程序执行结果:
hello 执行线程:ForkJoinPool.commonPool-worker-1
world 执行线程:ForkJoinPool.commonPool-worker-1
result 执行线程:main
获取结果 执行线程:main
两个异步任务合并结果:WORLD,HELLO
thenCombineAsync 会在两个CompletableFuture任务都执行完成后,再用一个异步线程把两个任务的结果一块处理
public class ThenCombineDemo02 {
public static void main(String[] args) {
ExecutorService threadPool = Executors.newFixedThreadPool(3);
CompletableFuture<String> helloAsync = CompletableFuture.supplyAsync(() -> {
System.out.println("hello 执行线程:" + Thread.currentThread().getName());
return "hello";
}, threadPool);
CompletableFuture<String> worldAsync = CompletableFuture.supplyAsync(() -> {
System.out.println("world 执行线程:" + Thread.currentThread().getName());
return "world";
}, threadPool);
CompletableFuture<String> result = worldAsync.thenCombineAsync(helloAsync, (hello, world) -> {
System.out.println("result 执行线程:" + Thread.currentThread().getName());
return (hello + "," + world).toUpperCase();
}, threadPool);
System.out.println("获取结果 执行线程:" + Thread.currentThread().getName());
System.out.println("两个异步任务合并结果:" + result.join());
}
}
thenCompose 方法允许你对两个CompletableFuture任务进行流水线操作,当第一个异步任务操作完成时,会将其结果作为参数传递给第二个任务。
public <U> CompletableFuture<U> thenCompose(Function<? super T, ? extends CompletionStage<U>> fn);
public <U> CompletableFuture<U> thenComposeAsync(Function<? super T, ? extends CompletionStage<U>> fn) ;
public <U> CompletableFuture<U> thenComposeAsync(Function<? super T, ? extends CompletionStage<U>> fn, Executor executor) ;
thenCompose当第一个异步任务操作完成时,会将其结果作为参数传递给第二个任务(第二个任务为串行化操作,由调用者线程执行)
public class ThenComposeDemo01 {
public static void main(String[] args) {
CompletableFuture<String> result = CompletableFuture.supplyAsync(() -> {
System.out.println("hello 执行线程:" + Thread.currentThread().getName());
return "hello";
}).thenCompose((hello -> {
System.out.println("thenCompose 执行线程:" + Thread.currentThread().getName());
return CompletableFuture.supplyAsync((hello + "world")::toUpperCase);
}));
System.out.println("获取结果 执行线程:" + Thread.currentThread().getName());
System.out.println("两个异步任务流水线执行结果:" + result.join());
}
}
程序执行情况:
hello 执行线程:ForkJoinPool.commonPool-worker-1
thenCompose 执行线程:main
获取结果 执行线程:main
两个异步任务流水线执行结果:HELLOWORLD
thenComposeAsync当第一个异步任务操作完成时,会将其结果作为参数传递给第二个任务(第二个任务仍为异步线程执行操作,可由默认ForkJoin线程池执行,也可使用自定义线程池)
public class ThenComposeDemo02 {
public static void main(String[] args) {
ExecutorService threadPool = Executors.newFixedThreadPool(2);
CompletableFuture<String> result = CompletableFuture.supplyAsync(() -> {
System.out.println("hello 执行线程:" + Thread.currentThread().getName());
return "hello";
},threadPool).thenComposeAsync((hello -> {
System.out.println("thenCompose 执行线程:" + Thread.currentThread().getName());
return CompletableFuture.supplyAsync((hello + "world")::toUpperCase);
}),threadPool);
System.out.println("获取结果 执行线程:" + Thread.currentThread().getName());
System.out.println("两个异步任务流水线执行结果:" + result.join());
}
}
程序执行情况:
hello 执行线程:pool-1-thread-1
获取结果 执行线程:main
thenCompose 执行线程:pool-1-thread-2
两个异步任务流水线执行结果:HELLOWORLD
CompleTableFuture还有很有很多有趣的方法,随着后续不断学习继续深入研究吧!