文章目录
CompletableFuture
Future接口理论知识复习
-
Future接口是jdk5开始支持的,CompletableFuture是jdk8开始支持的
-
Future接口(FutureTask实现类)定义了操作异步任务执行的一些方法,如获取异步任务的执行结果、取消任务的执行、判断任务是否被取消、判断任务执行是否完毕
public interface Future<V> { /** * 取消任务的执行 */ boolean cancel(boolean mayInterruptIfRunning); /** * 判断任务是否被取消 */ boolean isCancelled(); /** * 判断任务执行是否完毕 */ boolean isDone(); /** * 获取异步任务的执行结果 */ V get() throws InterruptedException, ExecutionException; /** * 一定时限内获取异步任务的执行结果 */ V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException; }
-
Future接口作用:比如主线程让一个子线程去执行任务,子线程可能比较耗时,启动子线程开始执行任务后,主线程就去做其它事情了,忙其它事情或者先执行完,过一会才去获取子线程的执行结果或变更的任务状态(老师上课时间想喝水,他继续讲课不结束上课这个主线程,让学生去小卖部帮老师买水完成这个耗时和费力的任务)
-
一句话:Future接口可以为主线程开一个分支任务,专门为主线程处理耗时和费力的复杂业务
Future接口常用实现FutureTask异步任务
Future接口能干什么
Future是Java5新加的一个接口,它提供一种异步并行计算的功能,如果主线程需要执行一个很耗时的计算任务,我们会就可以通过Future把这个任务放进异步线程中执行,主线程继续处理其他任务或者先行结束,再通过Future获取计算结果
相关接口
Runnable接口
Callable接口
Future接口和FutureTask实现类
目的
异步多线程任务执行且返回有结果,即三个特点:多线程、有返回、异步任务(班长为老师去买水作为新启动的异步多线程任务且买到水有结果返回)
Runnable接口和Callable接口区别
class MyThread1 implements Runnable {
@Override
public void run() {
}
}
class MyThread2 implements Callable<String> {
@Override
public String call() throws Exception {
return null;
}
}
可以看到实现Runnable接口和Callable接口后重写的方法的区别就是是否有返回值和是否抛异常,根据前面的目的,只有实现Callable接口,因为Callable接口有返回值才符合
Thread类构造方法
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
public Thread(ThreadGroup group, Runnable target) {
init(group, target, "Thread-" + nextThreadNum(), 0);
}
public Thread(String name) {
init(null, null, name, 0);
}
public Thread(ThreadGroup group, String name) {
init(group, null, name, 0);
}
public Thread(Runnable target, String name) {
init(null, target, name, 0);
}
public Thread(ThreadGroup group, Runnable target, String name) {
init(group, target, name, 0);
}
public Thread(ThreadGroup group, Runnable target, String name,
long stackSize) {
init(group, target, name, stackSize);
}
但是查看Thread类构造方法可以发现,想要创建一个线程接受的参数只有Runnable接口,而Runnable接口不满足有返回、异步任务的特点
所以需要找到一个接口,同时满足多线程、有返回、异步任务三个特点
最先想到就是找Runnable接口的子接口,要满足多线程和异步任务,可以看到子接口RunnableFuture接口,实现了Runnable接口、Future接口。但是还需要有返回值,可以看到FutureTask类,实现了RunnableFuture接口,但是FutureTask类并没有实现Callable接口,但是有一种设计模式,构造注入!
本源的Future接口相关架构
Future接口的继承关系
构造方法
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result);
this.state = NEW; // ensure visibility of callable
}
Code
package com.fastech.juc;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class CompletableFutureDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<String> futureTask = new FutureTask<>(new MyThread());
Thread t1 = new Thread(futureTask, "t1");
t1.start();
System.out.println(futureTask.get());
}
}
class MyThread implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println("---come in call()---");
return "hello Callable";
}
}
输出结果:
---come in call()---
hello Callable
Future编码实战和优缺点分析
优点
Future+线程池异步多线程任务配合,能显著提高程序的执行效率
案例
package com.bilibili.juc.completablefuture;
import java.util.concurrent.*;
public class FutureThreadPoolDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
m1();
// 3个任务,目前开启多个异步任务线程来处理,请问耗时多少?
ExecutorService threadPool = Executors.newFixedThreadPool(3);
long startTime = System.currentTimeMillis();
FutureTask<String> futureTask1 = new FutureTask<>(() -> {
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "task1 over";
});
threadPool.submit(futureTask1);
FutureTask<String> futureTask2 = new FutureTask<>(() -> {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "task2 over";
});
threadPool.submit(futureTask2);
System.out.println(futureTask1.get());
System.out.println(futureTask2.get());
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
long endTime = System.currentTimeMillis();
System.out.println("3个任务,目前开启多个异步任务线程来处理,耗时\t---costTime:" + (endTime - startTime) + "毫秒"); // ---costTime:869毫秒
System.out.println(Thread.currentThread().getName() + "\t ---end");
threadPool.shutdown();
// // 这种方式每增加一个任务都需要创建一个线程,所以可以使用线程池实现
// FutureTask<String> futureTask1 = new FutureTask<>(() -> {
// try {
// TimeUnit.MILLISECONDS.sleep(500);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// return "task1 over";
// });
// Thread t1 = new Thread(futureTask1, "t1");
// t1.start();
}
private static void m1() {
// 3个任务,目前只有一个线程main来处理,请问耗时多少?
long startTime = System.currentTimeMillis();
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
long endTime = System.currentTimeMillis();
System.out.println("3个任务,目前只有一个线程main来处理,耗时\t---costTime:" + (endTime - startTime) + "毫秒"); // ---costTime:1112毫秒
System.out.println(Thread.currentThread().getName() + "\t ---end");
}
}
输出结果:
3个任务,目前只有一个线程main来处理,耗时 ---costTime:11021毫秒
main ---end
task1 over
task2 over
3个任务,目前开启多个异步任务线程来处理,耗时 ---costTime:8248毫秒
main ---end
小总结
可以看到通过Future+线程池异步多线程任务配合,几乎可以节约三分之一的性能
缺点1
get()方法容易阻塞
案例
package com.fastech.juc.completablefuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
public class FutureAPIDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
FutureTask<String> futureTask = new FutureTask<>(() -> {
System.out.println(Thread.currentThread().getName() + "\t ---come in");
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "task over";
});
Thread t1 = new Thread(futureTask, "t1");
t1.start();
System.out.println(Thread.currentThread().getName() + "\t --- 忙其它任务了");
// System.out.println(futureTask.get()); // 一旦调用get()方法,就会非要等到结果才会离开,不管是否计算完成,容器程序阻塞
System.out.println(futureTask.get(3, TimeUnit.SECONDS)); // 不愿意等待很长时间,我希望过时不候,可以自动离开,这里会抛TimeoutException异常,程序可以通过抓取这个异常避免阻塞
}
}
输出结果:
main --- 忙其它任务了
t1 ---come in
Exception in thread "main" java.util.concurrent.TimeoutException
at java.util.concurrent.FutureTask.get(FutureTask.java:205)
at com.fastech.juc.completablefuture.FutureAPIDemo.main(FutureAPIDemo.java:32)
小总结
- get方法容易阻塞,一般放在程序后面。一旦调用get()方法,就会非要等到结果才会离开,不管是否计算完成,容器程序阻塞
- 假如不愿意等待很长时间,希望过时不候,可以自动离开,使用
get(long timeout, TimeUnit unit)
方法,超时未获取到结果会抛TimeoutException异常,程序可以通过抓取这个异常避免阻塞
缺点2
isDone()方法轮询,容易导致cpu空断,耗费更多的性能资源
案例
package com.fastech.juc.completablefuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
public class FutureAPIDemo2 {
public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
FutureTask<String> futureTask = new FutureTask<>(() -> {
System.out.println(Thread.currentThread().getName() + "\t ---come in");
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "task over";
});
Thread t1 = new Thread(futureTask, "t1");
t1.start();
System.out.println(Thread.currentThread().getName() + "\t --- 忙其它任务了");
while (true) {
if (futureTask.isDone()) {
System.out.println(futureTask.get());
break;
} else {
try {
TimeUnit.MILLISECONDS.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("正在处理中。。。");
}
}
}
}
输出结果:
main --- 忙其它任务了
t1 ---come in
正在处理中。。。
正在处理中。。。
正在处理中。。。
正在处理中。。。
正在处理中。。。
正在处理中。。。
正在处理中。。。
正在处理中。。。
正在处理中。。。
正在处理中。。。
task over
小总结
- 轮询的方式会耗费无谓的CPU资源,而且也不见得能及时地得到计算结果
- 如果想要异步获取结果,通常都会以轮询地方式去获取结果,尽量不要阻塞
结论
Future对于结果的获取不是很友好,只能通过阻塞或者轮询的方式得到任务的结果
想完成一些复杂的任务
对于简单的业务场景使用Future完全OK,但是对于高并发复杂的业务场景,需要不停的轮询或者容易导致阻塞,或多或少都有一些潜在的风险,另外,我们更希望Future这个异步任务获取返回值功能更加强大!具体需求如下
回调通知
应对Future的完成时间,完成了可以告诉我,也就是我们的回调通知
通过轮询的方式去判断任务是否完成这样非常占CPU并且代码也不优雅
创建异步任务
尽量把复杂的交给异步任务,主干还是专注自己的业务逻辑,不牵扯在一起,可以使用前面演示的Future+线程池配合实现
多个任务前后依赖可以组合处理(水煮鱼)
想将多个异步任务的计算结果组合起来,后一个异步任务的计算结果需要前一个异步任务的值
将两个或多个异步计算合成一个异步计算,这几个异步计算相互独立,同时后面这个又依赖前一个处理的结果
例如:ps -ef|grep tomcat,这个linux系统的命令就是在ps -ef结果集中找到带有tomcat的结果集
对计算速度选最快
当Future集合中某个任务最快结束时,返回结果,返回第一名处理结果。
例如:两个人比赛玩消消乐,谁先完成就显示谁win
…
CompletableFuture引入
通过上面想完成一些复杂的任务,使用Future之前提供的那点API就囊中羞涩,处理起来不够优雅,这时候还是让CompletableFuture以声明式的方式优雅的处理这些需求
从i(Future)到i++(CompletableFuture),Future能干的,CompletableFuture都能干!
CompletableFuture对Future的改进
CompletableFuture为什么出现
- get()方法在Future计算完成之前会一直处于阻塞状态下,阻塞的方式和异步编程的设计理念相违背
- isDone()方法容易耗费CPU资源(CPU空转)
- 对于真正的异步处理我们希望是可以通过传入回调函数,在Future结束时自动调用该回调函数,这样,我们就不用等待结果了
jdk8设计出CompletableFuture,CompletableFuture提供了一种观察者模式类似的机制,可以让任务执行完成后通知监听的一方。
CompletableFuture和CompletionStage源码介绍
类架构说明
接口CompletionStage
- CompletionStage代表异步计算过程中的某一个阶段,一个阶段完成以后可能会触发另外一个阶段,有些类似linux系统的管道分隔符传参数
- 一个阶段的计算执行可以是一个Function,Consumer或者Runnable。比如stage.thenApply(x -> square(x)).thenAccept(x -> System.out.println(x)).thenRun(() - > System.out.println())
- 一个阶段的执行可能是被单个阶段的完成触发,也可能是由多个阶段一起触发
类CompletableFuture
- CompletableFuture提供了非常强大的Future的扩展功能,可以帮助我们简化异步编程的复杂性,并且提供了函数式编程的能力,可以通过回调的方式处理计算结果,也提供了转换和组合CompletableFuture的方法
- 它可能代表一个明确完成的Future,也可能代表一个完成阶段(CompletionStage),它支持在计算完成以后触发一些函数或执行某些动作
核心的四个静态方法,来创建一个异步任务
默认无参构造(不推荐使用)
/**
* 创建一个新的不完整的CompletableFuture
*/
public CompletableFuture() {
}
runAsync无返回值
public static CompletableFuture<Void> runAsync(Runnable runnable) {
return asyncRunStage(asyncPool, runnable);
}
public static CompletableFuture<Void> runAsync(Runnable runnable,
Executor executor) {
return asyncRunStage(screenExecutor(executor), runnable);
}
supplyAsync有返回值
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier) {
return asyncSupplyStage(asyncPool, supplier);
}
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier,
Executor executor) {
return asyncSupplyStage(screenExecutor(executor), supplier);
}
ps:Supplier是一个供给型函数式接口,特点:没输入参数有返回值
上述Executor executor参数说明
- 没有指定Executor的方法,直接使用默认的ForkJoinPool.commonPool()作为它的线程池执行异步代码
- 如果指定线程池,则使用我们自定义或者特别指定的线程池执行异步代码
Code
runAsync无返回值
不指定线程池
package com.fastech.juc.completablefuture;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
public class CompletableFutureBuildDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(() -> {
System.out.println(Thread.currentThread().getName());
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println(completableFuture.get());
}
}
输出结果:
ForkJoinPool.commonPool-worker-1
null
指定线程池
package com.fastech.juc.completablefuture;
import java.util.concurrent.*;
public class CompletableFutureBuildDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService threadPool = Executors.newFixedThreadPool(3);
CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(() -> {
System.out.println(Thread.currentThread().getName());
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, threadPool);
System.out.println(completableFuture.get());
threadPool.shutdown();
}
}
输出结果:
pool-1-thread-1
null
supplyAsync有返回值
不指定线程池
package com.fastech.juc.completablefuture;
import java.util.concurrent.*;
public class CompletableFutureBuildDemo2 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName());
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "hello supplyAsync";
});
System.out.println(completableFuture.get());
}
}
输出结果:
ForkJoinPool.commonPool-worker-1
hello supplyAsync
指定线程池
package com.fastech.juc.completablefuture;
import java.util.concurrent.*;
public class CompletableFutureBuildDemo2 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService threadPool = Executors.newFixedThreadPool(3);
CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName());
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "hello supplyAsync";
}, threadPool);
System.out.println(completableFuture.get());
threadPool.shutdown();
}
}
输出结果:
pool-1-thread-1
hello supplyAsync
Code之通用演示,减少阻塞和轮询
从Java8开始引入了CompletableFuture,它是Future的功能增强版,减少阻塞和轮询,可以传入回调对象,当异步任务完成或者发生异常时,自动调用回调对象的回调方法
实现Future的功能
package com.fastech.juc.completablefuture;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
public class CompletableFutureUseDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + "---come in");
int result = ThreadLocalRandom.current().nextInt(10);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("---1秒钟后出结果:" + result);
return result;
});
System.out.println(Thread.currentThread().getName() + "线程先去忙其它任务");
System.out.println(completableFuture.get());
}
}
输出结果:
main线程先去忙其它任务
ForkJoinPool.commonPool-worker-1---come in
---1秒钟后出结果:2
2
增强功能
package com.fastech.juc.completablefuture;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
public class CompletableFutureUseDemo2 {
public static void main(String[] args) {
CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + "---come in");
int result = ThreadLocalRandom.current().nextInt(10);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("---1秒钟后出结果:" + result);
return result;
}).whenComplete((v, e) -> {
if (e == null) {
System.out.println("---计算完成,更新系统UpdateValue:" + v);
}
}).exceptionally(e -> {
e.printStackTrace();
System.out.println("异常情况:" + e.getCause() + "\t" + e.getMessage());
return null;
});
System.out.println(Thread.currentThread().getName() + "线程先去忙其它任务");
}
}
输出结果:
ForkJoinPool.commonPool-worker-1---come in
main线程先去忙其它任务
疑问:为什么没有打印出—1秒钟后出结果:和—计算完成,更新系统UpdateValue:?
解释下为什么默认线程池关闭,自定义线程池记得关闭。因为主线程结束了,CompletableFuture默认使用的线程池就会立刻关闭
故代码修改为:
package com.fastech.juc.completablefuture;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
public class CompletableFutureUseDemo2 {
public static void main(String[] args) {
CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + "---come in");
int result = ThreadLocalRandom.current().nextInt(10);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("---1秒钟后出结果:" + result);
return result;
}).whenComplete((v, e) -> {
if (e == null) {
System.out.println("---计算完成,更新系统UpdateValue:" + v);
}
}).exceptionally(e -> {
e.printStackTrace();
System.out.println("异常情况:" + e.getCause() + "\t" + e.getMessage());
return null;
});
System.out.println(Thread.currentThread().getName() + "线程先去忙其它任务");
// 主线程不要立刻结束,否则CompletableFuture默认使用的线程池就会立刻关闭,暂停3秒主线程
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
输出结果:
ForkJoinPool.commonPool-worker-1---come in
main线程先去忙其它任务
---1秒钟后出结果:0
---计算完成,更新系统UpdateValue:0
但是在实际工作中还是建议使用自定义线程池
最终代码如下:
package com.fastech.juc.completablefuture;
import java.util.concurrent.*;
public class CompletableFutureUseDemo2 {
public static void main(String[] args) {
ExecutorService threadPool = Executors.newFixedThreadPool(3);
try {
CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + "---come in");
int result = ThreadLocalRandom.current().nextInt(10);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("---1秒钟后出结果:" + result);
return result;
}, threadPool).whenComplete((v, e) -> {
if (e == null) {
System.out.println("---计算完成,更新系统UpdateValue:" + v);
}
}).exceptionally(e -> {
e.printStackTrace();
System.out.println("异常情况:" + e.getCause() + "\t" + e.getMessage());
return null;
});
System.out.println(Thread.currentThread().getName() + "线程先去忙其它任务");
} catch (Exception e) {
e.printStackTrace();
} finally {
threadPool.shutdown();
}
}
}
输出结果:
pool-1-thread-1---come in
main线程先去忙其它任务
---1秒钟后出结果:9
---计算完成,更新系统UpdateValue:9
手动制造异常情况测试
package com.fastech.juc.completablefuture;
import java.util.concurrent.*;
public class CompletableFutureUseDemo2 {
public static void main(String[] args) {
ExecutorService threadPool = Executors.newFixedThreadPool(3);
try {
CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + "---come in");
int result = ThreadLocalRandom.current().nextInt(10);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("---1秒钟后出结果:" + result);
if (result > 5) {
int i = 10 / 0;
}
return result;
}, threadPool).whenComplete((v, e) -> {
if (e == null) {
System.out.println("---计算完成,更新系统UpdateValue:" + v);
}
}).exceptionally(e -> {
e.printStackTrace();
System.out.println("异常情况:" + e.getCause() + "\t" + e.getMessage());
return null;
});
System.out.println(Thread.currentThread().getName() + "线程先去忙其它任务");
} catch (Exception e) {
e.printStackTrace();
} finally {
threadPool.shutdown();
}
}
}
输出结果:
pool-1-thread-1---come in
main线程先去忙其它任务
---1秒钟后出结果:6
异常情况:java.lang.ArithmeticException: / by zero java.lang.ArithmeticException: / by zero
java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
at java.util.concurrent.CompletableFuture.encodeThrowable(CompletableFuture.java:273)
at java.util.concurrent.CompletableFuture.completeThrowable(CompletableFuture.java:280)
at java.util.concurrent.CompletableFuture$AsyncSupply.run$$$capture(CompletableFuture.java:1606)
at java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.ArithmeticException: / by zero
at com.fastech.juc.completablefuture.CompletableFutureUseDemo2.lambda$main$0(CompletableFutureUseDemo2.java:26)
at java.util.concurrent.CompletableFuture$AsyncSupply.run$$$capture(CompletableFuture.java:1604)
... 4 more
CompletableFuture的优点
- 异步任务结束时,会自动回调某个对象的方法
- 主线程设置好回调后,不再关心异步任务的执行,异步任务之间可以顺序执行
- 异步任务出错时,会自动回调
案例精讲-从电商网站的比价需求展开
函数式编程已成为主流
大厂面试题看看
Lambda表达式+Stream流式调用+Chain链式调用+Java8函数式编程
函数式接口
- Runnable:无参数、无返回值
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
- Function:功能型函数式接口,接受一个参数,并且有返回值
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
}
- Consumer:消费型函数式接口,接受一个参数,没有返回值
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}
- BiConsumer:接受两个参数,没有返回值
@FunctionalInterface
public interface BiConsumer<T, U> {
void accept(T t, U u);
}
- Supplier:供给型函数式接口,没有参数,有返回值
@FunctionalInterface
public interface Supplier<T> {
T get();
}
小总结
chain链式调用
package com.bilibili.juc.completablefuture;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
public class CompletableFutureMallDemo {
public static void main(String[] args) {
// 老写法
Student student1 = new Student();
student1.setId(1);
student1.setStudentName("zhangsan");
student1.setMajor("java");
// chain链式调用
Student student2 = new Student();
student2.setId(2).setStudentName("lisi").setMajor("python");
}
}
@AllArgsConstructor
@NoArgsConstructor
@Data
@Accessors(chain = true) // 开启chain链式调用
class Student {
private Integer id;
private String studentName;
private String major;
}
先说说join和get对比
join和get区别在于:join在编译期间不会报检查性异常,运行时该报错报错,而get在编译期间报检查性异常
package com.bilibili.juc.completablefuture;
import java.util.concurrent.CompletableFuture;
public class CompletableFutureMallDemo2 {
public static void main(String[] args) /*throws ExecutionException, InterruptedException*/ {
CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> "hello 1234");
// System.out.println(completableFuture.get());
System.out.println(completableFuture.join());
}
}
输出结果:
hello 1234
说说你过去工作中的项目亮点?
见以下案例实战
大厂业务需求说明
*切记:功能—>性能(完成—>完美)
电商网站比价需求分析
需求说明
- 同一款产品,同时搜索出同款产品在各大电商平台的售价
- 同一款产品,同时搜索出本产品在同一个电商平台下,各个入驻卖家售价是多少
输出返回
出来结果希望是同款产品的在不同地方的价格清单列表,返回一个List<String>
例如:
《Mysql》 in jd price is 88.05
《Mysql》 in taobao price is 90.43
《Mysql》 in dangdang price is 86.11
解决方案
对比同一个产品在各个平台上的价格,要求获得一个清单列表
- step by step,按部就班,查完淘宝查京东,查完京东查天猫…
- all in,万箭齐发,一口气多线程异步任务同时查询
一波流Java8函数式编程带走-比价案例实战Case
package com.bilibili.juc.completablefuture;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
import lombok.NoArgsConstructor;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
public class CompletableFutureMallCase {
static List<NetMall> list = Arrays.asList(new NetMall("jd"), new NetMall("taobao"), new NetMall("dangdang"));
/**
* step by step
* @param list
* @param productName
* @return
*/
public static List<String> getPrice(List<NetMall> list, String productName) {
//《Mysql》 in jd price is 88.05
return list
.stream()
.map(netMall ->
String.format("《" + productName + "》" + "in %s price is %.2f",
netMall.getNetMallName(),
netMall.calcPrice(productName)))
.collect(Collectors.toList());
}
/**
* all in
* 把list里面的内容映射给CompletableFuture()
* @param list
* @param productName
* @return
*/
public static List<String> getPriceByCompletableFuture(List<NetMall> list, String productName) {
return list
.stream()
.map(netMall ->
CompletableFuture.supplyAsync(() ->
String.format("《" + productName + "》" + "in %s price is %.2f",
netMall.getNetMallName(),
netMall.calcPrice(productName)))) // Stream<CompletableFuture<String>>
.collect(Collectors.toList()) // List<CompletableFuture<String>>
.stream() // Stream<CompletableFuture<String>>
.map(CompletableFuture::join) // Stream<String>
.collect(Collectors.toList()); // List<String>
}
public static void main(String[] args) {
/**
* 采用step by setp方式查询
* 《mysql》in jd price is 110.11
* 《mysql》in taobao price is 109.32
* 《mysql》in dangdang price is 109.24
* ------costTime: 3094 毫秒
*/
long startTime = System.currentTimeMillis();
List<String> list1 = getPrice(list, "mysql");
for (String element : list1) {
System.out.println(element);
}
long endTime = System.currentTimeMillis();
System.out.println("------costTime: " + (endTime - startTime) + " 毫秒");
System.out.println("-----------------------------------");
/**
* 采用 all in三个异步线程方式查询
* 《mysql》in jd price is 109.71
* 《mysql》in taobao price is 110.69
* 《mysql》in dangdang price is 109.28
* ------costTime: 1009 毫秒
*/
long startTime2 = System.currentTimeMillis();
List<String> list2 = getPriceByCompletableFuture(list, "mysql");
for (String element : list2) {
System.out.println(element);
}
long endTime2 = System.currentTimeMillis();
System.out.println("------costTime: " + (endTime2 - startTime2) + " 毫秒");
}
}
@AllArgsConstructor
@NoArgsConstructor
@Data
class NetMall {
private String netMallName;
public double calcPrice(String productName) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
return ThreadLocalRandom.current().nextDouble() * 2 + productName.charAt(0);
}
}
CompletableFuture常用方法
获得结果和触发计算
获取结果
- public T get() —>阻塞当前线程,直到获取CompletableFuture返回值为止
- public T get(long timeout,TimeUnit unit) —>阻塞当前线程指定时间,超时抛出TimeoutException
- public T join() —>和get一样的作用,只是不需要抛出异常
- public T getNow(T valuelfAbsent) —>计算完成就返回正常值,否则返回备胎值(设定的valuelfAbsent),立即获取结果不阻塞
主动触发计算
- public boolean complete(T value) ---->是否打断get方法立即返回括号值
Code
package com.bilibili.juc.completablefuture;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
public class CompletableFutureAPIDemo {
public static void main(String[] args) /*throws ExecutionException, InterruptedException, TimeoutException*/ {
CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "abc";
});
// System.out.println(completableFuture.get());
// System.out.println(completableFuture.get(2L, TimeUnit.SECONDS));
// System.out.println(completableFuture.join());
System.out.println(completableFuture.getNow("xxx"));
System.out.println(completableFuture.complete("completeValue") + "\t" + completableFuture.join());
}
}
输出结果:
xxx
true completeValue
对计算结果进行处理
thenApply
计算结果存在依赖关系,这两个线程串行化 ---->由于存在依赖关系(当前步错,不走下一步),当前步骤有异常的话就叫停
Code
package com.bilibili.juc.completablefuture;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class CompletableFutureAPI2Demo {
public static void main(String[] args) {
ExecutorService threadPool = Executors.newFixedThreadPool(3);
CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("--------步骤一--------");
return 1;
}, threadPool).thenApply(f -> {
int i = 10 / 0;
System.out.println("--------步骤二--------");
return f + 2;
}).thenApply(f -> {
System.out.println("--------步骤三--------");
return f + 3;
}).whenComplete((v, e) -> {
if (e == null) {
System.out.println("--------计算结果:" + v);
}
}).exceptionally(e -> {
e.printStackTrace();
System.out.println(e.getMessage());
return null;
});
System.out.println(Thread.currentThread().getName() + "--------主线程先去忙其它任务--------");
threadPool.shutdown();
}
}
输出结果:
main--------主线程先去忙其它任务--------
--------步骤一--------
java.lang.ArithmeticException: / by zero
java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
at java.util.concurrent.CompletableFuture.encodeThrowable(CompletableFuture.java:273)
at java.util.concurrent.CompletableFuture.completeThrowable(CompletableFuture.java:280)
at java.util.concurrent.CompletableFuture.uniApply(CompletableFuture.java:618)
at java.util.concurrent.CompletableFuture$UniApply.tryFire(CompletableFuture.java:591)
at java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:488)
at java.util.concurrent.CompletableFuture$AsyncSupply.run$$$capture(CompletableFuture.java:1609)
at java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.ArithmeticException: / by zero
at com.bilibili.juc.completablefuture.CompletableFutureAPI2Demo.lambda$main$1(CompletableFutureAPI2Demo.java:27)
at java.util.concurrent.CompletableFuture.uniApply(CompletableFuture.java:616)
... 7 more
handle
计算结果存在依赖关系,这两个线程串行化 ---->有异常也可以往下走一步,根据带的异常参数可以进一步处理
Code
package com.bilibili.juc.completablefuture;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class CompletableFutureAPI3Demo {
public static void main(String[] args) {
ExecutorService threadPool = Executors.newFixedThreadPool(3);
CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("--------步骤一--------");
return 1;
}, threadPool).handle((f, e) -> {
int i = 10 / 0;
System.out.println("--------步骤二--------");
return f + 2;
}).handle((f, e) -> {
System.out.println("--------步骤三--------");
return f + 3;
}).whenComplete((v, e) -> {
if (e == null) {
System.out.println("--------计算结果:" + v);
}
}).exceptionally(e -> {
e.printStackTrace();
System.out.println(e.getMessage());
return null;
});
System.out.println(Thread.currentThread().getName() + "--------主线程先去忙其它任务--------");
threadPool.shutdown();
}
}
输出结果:
main--------主线程先去忙其它任务--------
--------步骤一--------
--------步骤三--------
java.lang.NullPointerException
java.util.concurrent.CompletionException: java.lang.NullPointerException
at java.util.concurrent.CompletableFuture.encodeThrowable(CompletableFuture.java:273)
at java.util.concurrent.CompletableFuture.completeThrowable(CompletableFuture.java:280)
at java.util.concurrent.CompletableFuture.uniHandle(CompletableFuture.java:838)
at java.util.concurrent.CompletableFuture$UniHandle.tryFire(CompletableFuture.java:811)
at java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:488)
at java.util.concurrent.CompletableFuture$AsyncSupply.run$$$capture(CompletableFuture.java:1609)
at java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.NullPointerException
at com.bilibili.juc.completablefuture.CompletableFutureAPI3Demo.lambda$main$2(CompletableFutureAPI3Demo.java:32)
at java.util.concurrent.CompletableFuture.uniHandle(CompletableFuture.java:836)
... 7 more
小总结
对计算结果进行消费
thenAccept
接受任务的处理结果,并消费处理,无返回结果
Code
package com.bilibili.juc.completablefuture;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class CompletableFutureAPI4Demo {
public static void main(String[] args) {
CompletableFuture.supplyAsync(() -> 1)
.thenApply(f -> f + 2)
.thenApply(f -> f + 3)
.thenAccept(System.out::println);
}
}
输出结果:
6
对比补充
Code之任务之间的顺序执行
- thenRun(Runnable runnable): 任务A执行完执行B,并且B不需要A的结果
- thenAccept(Consumer action): 任务A执行完执行B,B需要A的结果,但是任务B没有返回值
- thenApply(Function fn): 任务A执行完执行B,B需要A的结果,同时任务B有返回值
Code
package com.bilibili.juc.completablefuture;
import java.util.concurrent.CompletableFuture;
public class CompletableFutureAPI5Demo {
public static void main(String[] args) {
System.out.println(CompletableFuture.supplyAsync(() -> "resultA").thenRun(() -> {}).join());
System.out.println("----------------");
System.out.println(CompletableFuture.supplyAsync(() -> "resultA").thenAccept(System.out::println).join());
System.out.println("----------------");
System.out.println(CompletableFuture.supplyAsync(() -> "resultA").thenApply(r -> r + "resultB").join());
}
}
输出结果:
null
----------------
resultA
null
----------------
resultAresultB
CompletableFuture和线程池说明
以thenRun和thenRunAsync为例,有什么区别?
Code
package com.bilibili.juc.completablefuture;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class CompletableFutureWithThreadPoolDemo {
public static void main(String[] args) {
ExecutorService threadPool = Executors.newFixedThreadPool(5);
try {
CompletableFuture<Void> completableFuture = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.MILLISECONDS.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("1号任务" + "\t" + Thread.currentThread().getName());
return "abcd";
}, threadPool).thenRunAsync(() -> {
try {
TimeUnit.MILLISECONDS.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("2号任务" + "\t" + Thread.currentThread().getName());
}).thenRun(() -> {
try {
TimeUnit.MILLISECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("3号任务" + "\t" + Thread.currentThread().getName());
}).thenRun(() -> {
try {
TimeUnit.MILLISECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("4号任务" + "\t" + Thread.currentThread().getName());
});
System.out.println(completableFuture.get(2L, TimeUnit.SECONDS));
} catch (Exception e) {
e.printStackTrace();
} finally {
threadPool.shutdown();
}
}
}
输出结果:
1号任务 pool-1-thread-1
2号任务 ForkJoinPool.commonPool-worker-1
3号任务 ForkJoinPool.commonPool-worker-1
4号任务 ForkJoinPool.commonPool-worker-1
null
package com.bilibili.juc.completablefuture;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class CompletableFutureWithThreadPoolDemo {
public static void main(String[] args) {
ExecutorService threadPool = Executors.newFixedThreadPool(5);
try {
CompletableFuture<Void> completableFuture = CompletableFuture.supplyAsync(() -> {
System.out.println("1号任务" + "\t" + Thread.currentThread().getName());
return "abcd";
}, threadPool).thenRun(() -> {
try {
TimeUnit.MILLISECONDS.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("2号任务" + "\t" + Thread.currentThread().getName());
}).thenRun(() -> {
try {
TimeUnit.MILLISECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("3号任务" + "\t" + Thread.currentThread().getName());
}).thenRun(() -> {
try {
TimeUnit.MILLISECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("4号任务" + "\t" + Thread.currentThread().getName());
});
System.out.println(completableFuture.get(2L, TimeUnit.SECONDS));
} catch (Exception e) {
e.printStackTrace();
} finally {
threadPool.shutdown();
}
}
}
输出结果:
1号任务 pool-1-thread-1
2号任务 main
3号任务 main
4号任务 main
null
小总结
- 如果没有传入自定义线程池,都用默认线程池ForkJoinPool
- 传入了一个自定义线程池。如果你执行第一个任务时,传入了一个自定义线程池
- 调用thenRun方法执行第二个任务时,则第二个任务和第一个任务时共用同一个线程池
- 调用thenRunAsync执行第二个任务时,则第一个任务使用的是你自定义的线程池,第二个任务使用的是ForkJoin线程池
- 如果第一个任务耗时非常低,可能是线程处理太快,系统优化切换原则, 调用thenRun方法执行第二、三…n个任务时,直接使用main线程处理
- thenAccept和thenAcceptAsync,thenApply和thenApplyAsync等,之间的区别同理。
源码分析
// ForkJoinPool.getCommonPoolParallelism()获取系统CPU核数,现在CPU核数肯定都大于1,所以thenRunAsync()默认使用ForkJoinPool线程池
private static final boolean useCommonPool =
(ForkJoinPool.getCommonPoolParallelism() > 1);
private static final Executor asyncPool = useCommonPool ?
ForkJoinPool.commonPool() : new ThreadPerTaskExecutor();
public CompletableFuture<Void> thenRun(Runnable action) {
return uniRunStage(null, action);
}
public CompletableFuture<Void> thenRunAsync(Runnable action) {
return uniRunStage(asyncPool, action);
}
public CompletableFuture<Void> thenRunAsync(Runnable action,
Executor executor) {
return uniRunStage(screenExecutor(executor), action);
}
对计算速度进行选用
applyToEither
谁快用谁
Code
package com.bilibili.juc.completablefuture;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
public class CompletableFutureFastDemo {
public static void main(String[] args) {
CompletableFuture<String> playA = CompletableFuture.supplyAsync(() -> {
System.out.println("playA come in");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "playA";
});
CompletableFuture<String> playB = CompletableFuture.supplyAsync(() -> {
System.out.println("playB come in");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "playB";
});
CompletableFuture<String> result = playA.applyToEither(playB, f -> f + " is winner");
System.out.println(Thread.currentThread().getName() + "线程执行结束\t" + "--------: " + result.join());
}
}
输出结果:
playA come in
playB come in
main线程执行结束 --------: playA is winner
对计算结果进行合并
thenCombine
两个CompletableStage任务都完成后,最终能把两个任务的结果一起交给thenCombine来处理
注意:先完成的先等着,等待其他分支任务
Code标准版
package com.bilibili.juc.completablefuture;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
public class CompletableFutureCombineDemo {
public static void main(String[] args) {
CompletableFuture<Integer> completableFuture1 = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + "\t --------启动");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
return 10;
});
CompletableFuture<Integer> completableFuture2 = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + "\t --------启动");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
return 20;
});
CompletableFuture<Integer> result = completableFuture1.thenCombine(completableFuture2, (x, y) -> {
System.out.println("--------开始两个结果合并--------");
return x + y;
});
System.out.println(result.join());
}
}
输出结果:
ForkJoinPool.commonPool-worker-1 --------启动
ForkJoinPool.commonPool-worker-2 --------启动
--------开始两个结果合并--------
30
Code家庭作业
package com.bilibili.juc.completablefuture;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class CompletableFutureCombineDemo2 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
CompletableFuture<Integer> thenCombineResult = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + "\t" + "--------come in 1--------");
return 10;
}).thenCombine(CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + "\t" + "--------come in 2--------");
return 20;
}), (x, y) -> {
System.out.println(Thread.currentThread().getName() + "\t" + "--------1和2合并为3--------");
return x + y;
}).thenCombine(CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + "\t" + "--------come in 4--------");
return 30;
}), (a, b) -> {
System.out.println(Thread.currentThread().getName() + "\t" + "--------3和4合并为最终结果--------");
return a + b;
});
System.out.println("--------主线程结束--------");
System.out.println("最终结果:" + thenCombineResult.get());
}
}
输出结果:
ForkJoinPool.commonPool-worker-1 --------come in 1--------
ForkJoinPool.commonPool-worker-1 --------come in 2--------
main --------1和2合并为3--------
ForkJoinPool.commonPool-worker-1 --------come in 4--------
main --------3和4合并为最终结果--------
--------主线程结束--------
最终结果:60