强大的CompletableFuture
引子
为了让程序更加高效,让CPU最大效率的工作,我们会采用异步编程。首先想到的是开启一个新的线程去做某项工作。再进一步,为了让新线程可以返回一个值,告诉主线程事情做完了,于是乎Future粉墨登场。然而Future提供的方式是主线程主动问询新线程,要是有个回调函数就爽了。所以,为了满足Future的某些遗憾,强大的CompletableFuture随着Java8一起来了。
Future
传统多线程的却让程序更加高效,毕竟是异步,可以让CPU充分工作,但这仅限于新开的线程无需你的主线程再费心了。比如你开启的新线程仅仅是为了计算1+...+n再打印结果。有时候你需要子线程返回计算结果,在主线程中进行进一步计算,就需要Future了。
看下面这个例子,主线程计算2+4+6+8+10;子线程计算1+3+5+7+9;最后需要在主线程中将两部分结果再相加。
public class OddNumber implements Callable<Integer> {
@Override
public Integer call() throws Exception {
Thread.sleep(3000);
int result = 1 + 3 + 5 + 7 + 9;
return result;
}
}
public class FutureTest {
public static void main(String[] args) {
ExecutorService executor = Executors.newCachedThreadPool();
OddNumber oddNumber = new OddNumber();
Future<Integer> future = executor.submit(oddNumber);
long startTime = System.currentTimeMillis();
int evenNumber = 2 + 4 + 6 + 8 + 10;
try {
Thread.sleep(1000);
System.out.println("0.开始了:"+ (System.currentTimeMillis()-startTime) +"秒");
int oddNumberResult = future.get();//这时间会被阻塞
System.out.println("1+2+...+9+10="+(evenNumber+oddNumberResult));
System.out.println("1.开始了:"+ (System.currentTimeMillis()-startTime) +"秒");
} catch (Exception e) {
System.out.println(e);
}
}
}
输出结果:
0.开始了:1001秒
1+2+...+9+10=55
1.开始了:3002秒
看一下Future接口,只有五个方法比较简单
//取消任务,如果已经完成或者已经取消,就返回失败
boolean cancel(boolean mayInterruptIfRunning);
//查看任务是否取消
boolean isCancelled();
//查看任务是否完成
boolean isDone();
//刚才用到了,查看结果,任务未完成就一直阻塞
V get() throws InterruptedException, ExecutionException;
//同上,但是加了一个过期时间,防止长时间阻塞,主线程也做不了事情
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
CompletableFuture
上面的看到Future的五个方法,不是很丰富,既然我们的主线程叫做main,就应该以我为主,我更希望子线程做完了事情主动通知我。为此,Java8带来了CompletableFuture,一个Future的实现类。其实CompletableFuture最迷人的地方并不是极大丰富了Future的功能,而是完美结合了Java8流的新特性。
实现回调,自动后续操作
提前说一下CompletableFuture实现回调的方法(之一):thenAccept()
public CompletableFuture<Void> thenAccept(Consumer<? super T> action) {
return uniAcceptStage(null, action);
}
参数有个Consumer,用到了Java8新特性,行为参数化,就是参数不一定是基本类型或者类,也可以是函数(行为),或者说一个方法(接口)。
public class OddNumberPlus implements Supplier<Integer> {
@Override
public Integer get() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return 1+3+5+7+9;
}
}
public class CompletableFutureTest {
public static void main(String[] args) {
long startTime = System.currentTimeMillis();
final int evenNumber = 2 + 4 + 6 + 8 + 10;
CompletableFuture<Integer> oddNumber = CompletableFuture.supplyAsync(new OddNumberPlus());
try {
Thread.sleep(1000);
System.out.println("0.开始了:"+ (System.currentTimeMillis()-startTime) +"秒");
//看这里,实现回调
oddNumber.thenAccept(oddNumberResult->
{
System.out.println("1.开始了:"+ (System.currentTimeMillis()-startTime) +"秒");
System.out.println("此时计算结果为:"+(evenNumber+oddNumberResult));
});
oddNumber.get();
} catch (Exception e) {
System.out.println(e);
}
}
}
输出结果:
0.开始了:1006秒
1.开始了:3006秒
此时计算结果为:55
值得一提的是,本例中并没有显示的创建任务连接池,程序会默认选择一个任务连接池ForkJoinPool.commonPool()
private static final Executor asyncPool = useCommonPool ?
ForkJoinPool.commonPool() : new ThreadPerTaskExecutor();
ForkJoinPool始自JDK7,叫做分支/合并框架。可以通过将一个任务递归分成很多分子任务,形成不同的流,进行并行执行,同时还伴随着强大的工作窃取算法。极大的提高效率。当然,你也可以自己指定连接池。
CompletableFuture合并
Java8的确丰富了Future实现,CompletableFuture有很多方法可供大家使用,但是但从上面的例子来看,其实CompletableFuture能做的功能,貌似Future。毕竟你CompletableFuture用get()这个方法的时候还不是阻塞了,我Future蛮可以自己拿到返回值,再手动执行一些操作嘛(虽说这样main方法一定很不爽)。那么接下来的事情,Future做起来就十分麻烦了。假设我们main方法只做奇数合集加上偶数合集这一个操作,提前算这两个合集的操作异步交给两个子线程,我们需要怎么做呢?没错,开启两个线程,等到两个线程都计算结束的时候,我们进行最后的相加,问题在于,你怎么知道那个子线程最后结束的呢?(貌似可以做个轮询,不定的调用isDone()这个方法...)丰富的CompletableFuture功能为我们提供了一个方法,用于等待两个子线程都结束了,再进行相加操作:
//asyncPool就是上面提到的默认线程池ForkJoinPool
public <U,V> CompletableFuture<V> thenCombineAsync(
CompletionStage<? extends U> other,
BiFunction<? super T,? super U,? extends V> fn) {
return biApplyStage(asyncPool, other, fn);
}
看个例子:
public class OddCombine implements Supplier<Integer> {
@Override
public Integer get() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return 1+3+5+7+9;
}
}
public class EvenCombine implements Supplier<Integer> {
@Override
public Integer get() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return 2+4+6+8+10;
}
}
public class CompletableCombineTest {
public static void main(String[] args) throws Exception{
CompletableFuture<Integer> oddNumber = CompletableFuture.supplyAsync(new OddCombine());
CompletableFuture<Integer> evenNumber = CompletableFuture.supplyAsync(new EvenCombine());
long startTime = System.currentTimeMillis();
CompletableFuture<Integer> resultFuturn = oddNumber.thenCombine(evenNumber,(odd,even)->{
return odd + even;
});
System.out.println(resultFuturn.get());
System.out.println("0.开始了:"+ (System.currentTimeMillis()-startTime) +"秒");
}
}
输出结果:
55
0.开始了:3000秒
这边模拟一个睡1秒,一个睡3秒,但是真正的网络请求时间是不定的。是不是很爽,最爽的还不是现象,而是以上操作已经利用了Java8流的概念。
两个子线程还不够,那么还有**anyOff()**函数,可以承受多个CompletableFuture,会等待所有任务都完成。
public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs) {
return andTree(cfs, 0, cfs.length - 1);
}
与它长的很像的,有个方法,是当第一个执行结束的时候,就结束,后面任务不再等了,可以看作充分条件。
public static CompletableFuture<Object> anyOf(CompletableFuture<?>... cfs) {
return orTree(cfs, 0, cfs.length - 1);
}
在上面那个例子的基础上,把OddNumberPlus类时间调长一点:
public class OddNumberPlus implements Supplier<Integer> {
@Override
public Integer get() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return 1+3+5+7+9;
}
}
public class CompletableCombineTest {
public static void main(String[] args) throws Exception{
CompletableFuture<Integer> oddNumber = CompletableFuture.supplyAsync(new OddCombine());
CompletableFuture<Integer> evenNumber = CompletableFuture.supplyAsync(new EvenCombine());
CompletableFuture<Integer> testNumber = CompletableFuture.supplyAsync(new OddNumberPlus());
long startTime = System.currentTimeMillis();
CompletableFuture<Object> resultFuturn = CompletableFuture.anyOf(oddNumber,evenNumber,testNumber);
System.out.println(resultFuturn.get());
System.out.println("0.开始了:"+ (System.currentTimeMillis()-startTime) +"秒");
}
}
输出结果:
30
0.开始了:1000秒
小结
CompletableFuture的方法其实还有很多,常用的比如说runAsync(),类似于supplyAsync(),只是没有返回值;除了thenApply()可以加回调函数以外,还有thenApply();还有注入runAfterBoth()、runAfterEither(),这些见名知意。还有很多,可以点开CompletableFuture这个类的源码仔细看一看。见微知著,透过CompletableFuture,更加感觉到Java8的强大,强大的流概念、行为参数化、高效的并行理念等等,不仅让Java写起来更爽,还不断丰富Java整个生态。Java一直在进步,所以没有被时代淘汰,我们Javaer也可以继续职业生涯,感谢Java,一起进步。
BLOG地址:www.liangsonghua.me
关注微信公众号:松花皮蛋的黑板报,获取更多精彩!
公众号介绍:分享在京东工作的技术感悟,还有JAVA技术和业内最佳实践,大部分都是务实的、能看懂的、可复现的
强大的CompletableFuture的更多相关文章
- Runnable、Callable、Future和CompletableFuture
一.Runnable Runnable非常简单,只需要实现一个run方法即可,没有参数,也没有返回值.可以以new Thread的方式去运行,当然更好的方式在放到excutorPool中去运行. 二. ...
- ExecutorService的十个使用技巧
ExecutorService] (https://docs.oracle.com/javase/8/docs/api/java/util/concurrent /ExecutorService.ht ...
- 再来看看Java的新特性——其他新特性
关于剩余Java8新特性知识点总结,包含:默认方法.Optional.CompletableFuture.时间相关. 默认方法 默认方法皆在帮助Java新功能可以兼容低版本JDK已开发的程序. 比如说 ...
- 艾编程coding老师课堂笔记:java设计模式与并发编程笔记
设计模式概念 1.1 什么是设计模式 设计模式(Design Pattern)是前辈们对代码开发经验的总结,是解决特定问题的一系列套路.它不是语法规定,而是一套用来提高代码可复用性.可维护性.可读性. ...
- Java CompletableFuture 详解
Future是Java 5添加的类,用来描述一个异步计算的结果.你可以使用isDone方法检查计算是否完成,或者使用get阻塞住调用线程,直到计算完成返回结果,你也可以使用cancel方法停止任务的执 ...
- CompletableFuture基本用法
异步计算 所谓异步调用其实就是实现一个可无需等待被调用函数的返回值而让操作继续运行的方法.在 Java 语言中,简单的讲就是另启一个线程来完成调用中的部分计算,使调用继续运行或返回,而不需要等待计算结 ...
- Future、 CompletableFuture、ThreadPoolTaskExecutor简单实践
一 Future(jdk5引入) 简介: Future接口是Java多线程Future模式的实现,可以来进行异步计算. 可以使用isDone方法检查计算是否完成,或者使用get阻塞住调用线程,直到计算 ...
- Java 多线程中的任务分解机制-ForkJoinPool,以及CompletableFuture
ForkJoinPool的优势在于,可以充分利用多cpu,多核cpu的优势,把一个任务拆分成多个“小任务”,把多个“小任务”放到多个处理器核心上并行执行:当多个“小任务”执行完成之后,再将这些执行结果 ...
- Java8 CompletableFuture 编程
一.简介 所谓异步调用其实就是实现一个无需等待被调用函数的返回值而让操作继续运行的方法.在 Java 语言中,简单的讲就是另启一个线程来完成调用中的部分计算,使调用继续运行或返回,而不需要等待计算结 ...
随机推荐
- sbt 学习笔记(1)sbt安装和交互式界面使用
下载sbt: http://www.scala-sbt.org/download.html 解压zip文件F:\sbt-0.13.15 配置环境变量 如果需要可以修改F:\sbt-0.13.15\sb ...
- Nature Methods | 新软件SAVER-X可对单细胞转录组学数据进行有效降噪
图片来源(Nature Methods) 摘要 单细胞转 ...
- Nebula 架构剖析系列(零)图数据库的整体架构设计
Nebula Graph 是一个高性能的分布式开源图数据库,本文为大家介绍 Nebula Graph 的整体架构. 一个完整的 Nebula 部署集群包含三个服务,即 Query Service,S ...
- kafka-0.10.2.1:Producer生产时无法自动创建Topic
集群环境: CenterOS 1台 Kafka:0.10.2.1版本. 今天在测试环境下,我们的Kafka集群工作不正常,具体现象为,使用confulentkafka向kafka集群生产消息失败,且并 ...
- 【WPF on .NET Core 3.0】 Stylet演示项目 - 简易图书管理系统(1)
.NET Core 3.0已经发布了,除了一大堆令人激动的功能以外,也增加了对WPF的正式支持, 那么WPF在.NET Core 3.0下的开发体验如何呢? 本文利用了Stylet框架开发.NET C ...
- 理解numpy.dot()
import numpy.matlib import numpy as np a = np.array([[1,2],[3,4]]) b = np.array([[11,12],[13,14]]) p ...
- 对象模型(Object-Model):关于vptr、vtbl
当一个类本身定义了虚函数,或其父类有虚函数时,为了支持多态机制,编译器将为该类添加一个虚函数指针(vptr).虚函数指针一般都放在对象内存布局的第一个位置上,这是为了保证在多层继承或多重继承的情况下能 ...
- 《java编程思想》P160-P180(第八章部分+第九章部分)
1.什么是多态? 多态的定义:指允许不同类的对象对同一消息做出响应.即同一消息可以根据发送对象的不同而采用多种不同的行为方式.(发送消息就是函数调用) 现实中,关于多态的例子不胜枚举.比方说按下 F1 ...
- Qt5教程: (2) 信号与槽
1. 新建工程 新建一个"Qt Widgets Application"工程 2. 添加按钮 一个Qt工程会有很多个控件, 如果把逻辑代码都写在main函数里, main函数会非常 ...
- Did You AK Today? (今天你AK了吗?)
考虑到本文读者年龄原因,本文改为使用简体中文撰写. 题目描述 今有正整数 n,kn,kn,k,求 1−n1-n1−n 共 nnn 个数的全排列,按字典序的第 kkk 个. 数据满足 1≤n≤105,1 ...