多线程的利器:CompletableFuture 你还可以这样使用多线程
CompletableFuture是java8开始提供的API,解决了在多线程场景下繁琐的代码逻辑,日常开发中,我们都会用到线程池,一般会用execute()和submit()方法提交任务。但是当你用过CompletableFuture之后,就会发现以前的线程池处理任务有多难用,功能有多简陋,CompletableFuture又是多么简洁优雅。
要知道CompletableFuture已经随着Java8发布7年了,使用CompletableFuture 更便利使用多线程。
1. 使用线程池处理任务
@Test
public void Test27(){
// 1. 创建线程池
ExecutorService executorService = Executors.newFixedThreadPool(3);
List list = Arrays.asList(1, 2, 3);
// 2. 提交任务,并调用join()阻塞等待所有任务执行完成
CompletableFuture
.allOf(
list.stream().map(key ->
CompletableFuture.runAsync(() -> {
// 睡眠一秒,模仿处理过程
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
}
System.out.println("关注微信公众号Java编程Code,获取更多学习资料" + key);
}, executorService))
.toArray(CompletableFuture[]::new))
.join();
executorService.shutdown();
}
这样写法需要关注在于
-
获取结果时,调用的future.get()方法,会阻塞当前线程,直到返回结果,大大降低性能
-
有一半的代码在写怎么使用线程,其实我们不应该关心怎么使用线程,更应该关注任务的处理
2. 使用CompletableFuture重构任务处理
@Test
public void Test28(){
// 1. 创建线程池
ExecutorService executorService = Executors.newFixedThreadPool(3);
List list = Arrays.asList(1, 2, 3);
for (Integer key : list) {
// 2. 提交任务
CompletableFuture.supplyAsync(() -> {
// 睡眠一秒,模仿处理过程
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
}
return "关注微信公众号Java编程Code,获取更多学习资料 " + key;
}, executorService).whenCompleteAsync((result, exception) -> {
// 3. 获取结果
System.out.println(result);
});;
}
executorService.shutdown();
// 由于whenCompleteAsync获取结果的方法是异步的,所以要阻塞当前线程才能输出结果
try {
Thread.sleep(2000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
输出结果:

代码中使用了CompletableFuture的两个方法,
supplyAsync()方法作用是提交异步任务,有两个传参,任务和自定义线程池。
whenCompleteAsync()方法作用是异步获取结果,也有两个传参,结果和异常信息。
代码经过CompletableFuture改造后,是多么的简洁优雅。
提交任务也不用再关心线程池是怎么使用了,获取结果也不用再阻塞当前线程了
还想同步获取异步多线程返回结果,可以使用whenComplete()方法,或者单独调用join()方法。
join()方法配合lamba Stream流是这样用的:
@Test
public void Test29(){
// 1. 创建线程池
ExecutorService executorService = Executors.newFixedThreadPool(3);
List list = Arrays.asList(1, 2, 3);
// 2. 提交任务
List results = list.stream().map(key ->
CompletableFuture.supplyAsync(() -> {
// 睡眠一秒,模仿处理过程
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
}
return "关注微信公众号Java编程Code,获取更多学习资料" + key;
}, executorService))
.map(CompletableFuture::join).collect(Collectors.toList());
executorService.shutdown();
// 3. 获取结果
System.out.println(results);
}
输出结果:

多么的简洁优雅啊!原来executorService.submit()这种使用线程池的方式,可以彻底丢掉了
3. CompletableFuture更多妙用
3.1 等待所有任务执行完成
如果让你实现等待所有任务线程执行完成,再进行下一步操作,你会怎么做?
我猜你一定会使用 线程池+CountDownLatch,像下面这样:
@Test
public void Test30(){
// 1. 创建线程池
ExecutorService executorService = Executors.newFixedThreadPool(3);
List list = Arrays.asList(1, 2, 3);
CountDownLatch countDownLatch = new CountDownLatch(list.size());
for (Integer key : list) {
// 2. 提交任务
executorService.execute(() -> {
// 睡眠一秒,模仿处理过程
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
}
System.out.println("关注微信公众号Java编程Code,获取更多学习资料" + key);
countDownLatch.countDown();
});
}
executorService.shutdown();
// 3. 阻塞等待所有任务执行完成
try {
countDownLatch.await();
} catch (InterruptedException e) {
}
}
输出结果:

采用Java8的写法,看一下使用CompletableFuture是怎么重构的:
@Test
public void Test31(){
// 1. 创建线程池
ExecutorService executorService = Executors.newFixedThreadPool(3);
List list = Arrays.asList(1, 2, 3);
// 2. 提交任务,并调用join()阻塞等待所有任务执行完成
CompletableFuture
.allOf(
list.stream().map(key ->
CompletableFuture.runAsync(() -> {
// 睡眠一秒,模仿处理过程
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
}
System.out.println("关注微信公众号Java编程Code,获取更多学习资料" + key);
}, executorService))
.toArray(CompletableFuture[]::new))
.join();
executorService.shutdown();
}
输出结果:

代码看着有点乱,其实逻辑很清晰。
-
遍历list集合,提交CompletableFuture任务,把结果转换成数组
-
再把数组放到CompletableFuture的allOf()方法里面
-
最后调用join()方法阻塞等待所有任务执行完成
CompletableFuture的allOf()方法的作用就是,等待所有任务处理完成。
这样写是不是简洁优雅了许多?
3.2 任何一个任务处理完成就返回
如果要实现这样一个需求,往线程池提交一批任务,只要有其中一个任务处理完成就返回。
该怎么做?如果你手动实现这个逻辑的话,代码肯定复杂且低效,有了CompletableFuture就非常简单了,只需调用anyOf()方法就行了
@Test
public void Test32(){
// 1. 创建线程池
ExecutorService executorService = Executors.newFixedThreadPool(3);
List list = Arrays.asList(1, 2, 3,4,5);
long start = System.currentTimeMillis();
// 2. 提交任务
CompletableFuture completableFuture = CompletableFuture
.anyOf(
list.stream().map(key ->
CompletableFuture.supplyAsync(() -> {
// 睡眠一秒,模仿处理过程
try {
Thread.sleep(2000L);
} catch (InterruptedException e) {
}
return "关注微信公众号Java编程Code,获取更多学习资料" + key;
}, executorService))
.toArray(CompletableFuture[]::new));
executorService.shutdown();
// 3. 获取结果
System.out.println(completableFuture.join());
}
输出结果:

3.3 一个线程执行完成,交给另一个线程接着执行
有这么一个需求:
一个线程处理完成,把处理的结果交给另一个线程继续处理,怎么实现?
你是不是想到了一堆工具,线程池、CountDownLatch、Semaphore、ReentrantLock、Synchronized,该怎么进行组合使用呢?AB组合还是BC组合?
别瞎想了,来看一下CompletableFuture是怎么用的
@Test
public void Test33(){
// 1. 创建线程池
ExecutorService executorService = Executors.newFixedThreadPool(3);
// 2. 提交任务,并调用join()阻塞等待任务执行完成
String result2 = CompletableFuture.supplyAsync(() -> {
// 睡眠一秒,模仿处理过程
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
}
return "关注微信公众号Java编程Code,获取更多学习资料结果1";
}, executorService).thenApplyAsync(result1 -> {
// 睡眠一秒,模仿处理过程
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
}
return result1 + "关注微信公众号Java编程Code,获取更多学习资料结果2";
}, executorService).join();
executorService.shutdown();
// 3. 获取结果
System.out.println(result2);
}
输出结果:

代码主要用到了CompletableFuture的thenApplyAsync()方法,作用就是异步处理上一个线程的结果。
CompletableFuture真是太方便了
这么好用的CompletableFuture还有很多其他API。
4. CompletableFuture常用API
4.1 CompletableFuture常用API说明
-
提交任务
supplyAsync
runAsync
-
接力处理
thenRun thenRunAsync
thenAccept thenAcceptAsync
thenApply thenApplyAsync
handle handleAsync
applyToEither applyToEitherAsync
acceptEither acceptEitherAsync
runAfterEither runAfterEitherAsync
thenCombine thenCombineAsync
thenAcceptBoth thenAcceptBothAsync
API太多,有点眼花缭乱,很容易分类。
带run的方法,无入参,无返回值。
带accept的方法,有入参,无返回值。
带supply的方法,无入参,有返回值。
带apply的方法,有入参,有返回值。
带handle的方法,有入参,有返回值,并且带异常处理。
以Async结尾的方法,都是异步的,否则是同步的。
以Either结尾的方法,只需完成任意一个。
以Both/Combine结尾的方法,必须所有都完成。
-
获取结果
join 阻塞等待,不会抛异常
get 阻塞等待,会抛异常
complete(T value) 不阻塞,如果任务已完成,返回处理结果。如果没完成,则返回传参value。
completeExceptionally(Throwable ex) 不阻塞,如果任务已完成,返回处理结果。如果没完成,抛异常。
CompletableFuture 多线程巧妙使用分享就到此结束了,多谢阅览。
本文来自网络,不代表协通编程立场,如若转载,请注明出处:https://www.net2asp.com/7381ff0eda.html
