【尚硅谷周阳--JUC并发编程】【第二章--线程基础知识】【第三章CompletableFuture】
一、线程基础
1、start()方法简单介绍
- Java线程是通过start方法启动执行的,主要内容在native方法start0中,openjdk的写JNI一般是一一对应的,Thread.java对应的就是Thread.c
- start0其实就是JVM_StartThread。此时查看源代码可以看到在jvm.h中找到了声明,jvm.cpp中有实现
- openJDK源码下载地址:https://jdk.java.net/
- 在thread.c中start0方法调用了JVM_StartThread
总结来说就是:对于start0这个native方法,实质而言是通过JVM配合操作系统,底层由操作系统来给他分配了一个原生的基础线程来实现线程启动。
2、Java多线程相关概念
- 1把锁:synchronized
- 2个并:
- 并发:concurrent
- 是同一实体上的多个事件;
- 是在一台处理器上“同时”处理多个任务;
- 同一时刻,其实是只有一个事件在发生
- 并行:parallel
- 是在不同实体上的多个事件
- 是在多台处理器上同时处理多个任务
- 同一时刻,大家真的都在做事情,你做你的,我做我的,但是我们都在做
- 并发:concurrent
- 3个程:
- 进程:简单地说,在系统中运行的一个应用程序就是一个进程,每一个进程都有它自己的内存空间和系统资源。
- 线程:也被称为轻量级进程,再同一个进程内会有一个或多个线程,是大多数操作系统进行时序调度的基本单位。
- 管程:Monitor(监视器),也就是我们平常所说的锁。
- Monitor其实是一种同步机制,他的义务是保证(同一时间)只有一个线程可以访问被保护的数据和代码
- JVM中同步是基于进入和退出监视器对象(Monitor)来实现的,每个对象实例都会有一个Monitor对象
Object o = new Object(); new Thread(() -> { // o相当于监视器,monitor及管程 synchronized (o) { } }, "t2").start();
- Monitor对象会和Java对象一同创建并销毁,它底层是由C++语言来实现的。
3、用户线程和守护线程
3.1、Java线程分为用户线程和守护线程
- 一般情况下不做特别说明配置,默认都是用户线程。
- 用户线程(User Thread)
- 是系统的工作线程,它会完成这个程序需要完成的业务操作。
- 守护线程(Deamon Thread)
- 是一种特殊的线程为其他线程服务的,在后台默默地完成一些系统性的服务,比如垃圾回收线程就是典型的例子。
- 守护线程作为一个服务线程,没有服务对象就没有必要继续运行了,如果用户线程全部结束了,意味着程序需要完成的业务操作已经结束了,系统可以退出了。所以加入当系统只剩下守护现成的时候,Java虚拟机会自动退出。
3.2、线程的deamon属性
- 判断线程是否守护线程方法
// 返回结果为true即为守护线程
public final boolean isDaemon() {
return daemon;
}
// 设置线程是否守护线程方法
public final void setDaemon(boolean daemon) {
checkAccess();
this.daemon = daemon;
}
3.3、code演示
// 演示用户线程的独立性
public class DaemonDemo {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t 开始运行," +
(Thread.currentThread().isDaemon() ? "守护线程" : "用户线程"));
while (true) {
}
}, "t1");
t1.start();
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
// main线程结束了, 但是t1线程独立存活
System.out.println(Thread.currentThread().getName() + "\t ------end 主线程");
}
}
// 手动设置守护线程后,主线程结束后,守护线程也会被销毁
public class DaemonDemo {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t 开始运行," +
(Thread.currentThread().isDaemon() ? "守护线程" : "用户线程"));
while (true) {
}
}, "t1");
// 设置守护线程
t1.setDaemon(true);
t1.start();
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
// main线程结束后JVM自动关闭, 但是t1线程不能独立存活
System.out.println(Thread.currentThread().getName() + "\t ------end 主线程");
}
}
3.4、总结
如果用户线程全部结束,意味着程序需要完成的业务操作已经结束了,守护线程随着JVM一同结束工作,setDaemon(true)方法必须在start()之前设置,否者报IllegalThreadStateException不合法线程状态异常。
二、CompletableFuture
1、Future接口理论知识复习
- Future接口(FutureTask实现类)定义了操作异步任务执行一些方法,如获取异步任务的执行结果、取消任务的执行、判断任务是否被取消、判断任务执行是否完毕等。
- 比如主线程让一个子线程去执行任务,子线程可能比较耗时,启动了子线程开始执行任务后,主线程就去做其他事情了,忙其它事情或者先执行完,过了一会采取获取子任务的执行结果或变更的任务状态。
- 总结:Future接口可以为主线程开一个分支任务,专门为主线程处理耗时和费力的复杂业务。
2、Future接口常用实现类FutureTask异步任务
2.1、Future接口能干什么?
- Future是Java5新加的一个接口,它提供了一种异步并行计算的功能;如果主线程需要执行一个很耗时的计算任务,我们就可以通过Future把这个任务放到异步线程中执行,主线程继续处理其他任务或者先行结束,在通过Future获取计算结果。
- 目的:异步多线程任务执行且有返回有结果,三个特点:多线程/有返回/异步任务
2.2、本源的Future接口相关架构
FutureTask类实现了Runnable,Future,RunnableFuture接口,在构造方法中能传入Callable,能够满足三个特点:多线程/有返回/异步任务
// 构造方法
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
// 通过FutureTask实现带有返回值的异步多线程任务,并结合thread类获得处理结果
public class CompletableFutureDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<String> futureTask = new FutureTask<>(new MyThread2());
Thread t1 = new Thread(futureTask, "t1");
t1.start();
// 获取返回值
System.out.println(futureTask.get());
}
}
class MyThread implements Runnable {
// 接口没有返回值,不会抛出异常
@Override
public void run() {
}
}
class MyThread2 implements Callable<String> {
// 接口有返回值,会抛出异常
@Override
public String call() throws Exception {
System.out.println("------come in call()");
return "hello Callable";
}
}
2.3、Future编码实战和优缺点分析
-
优点
- future+线程池异步多线程任务配合,能显著提高程序的执行效率。
- 案例:
public class FutureThreadPoolDemo { public static void main(String[] args) throws ExecutionException, InterruptedException { // 三个任务,目前开启多个异步线程来处理,请问耗时多少 long startTime = System.currentTimeMillis(); ExecutorService threadPool = Executors.newFixedThreadPool(3); FutureTask<String> futureTask1 = new FutureTask<>(() -> { // 任务一:假设耗时500ms try { TimeUnit.MILLISECONDS.sleep(500); } catch (InterruptedException e) { throw new RuntimeException(e); } return "task1 over"; }); threadPool.submit(futureTask1); FutureTask<String> futureTask2 = new FutureTask<>(() -> { // 任务二:假设耗时300ms try { TimeUnit.MILLISECONDS.sleep(300); } catch (InterruptedException e) { throw new RuntimeException(e); } return "task2 over"; }); threadPool.submit(futureTask2); System.out.println(Thread.currentThread().getName() + "获取futureTask1 结果:" + futureTask1.get()); System.out.println(Thread.currentThread().getName() + "获取futureTask2 结果:" + futureTask2.get()); try { TimeUnit.MILLISECONDS.sleep(200); } catch (InterruptedException e) { throw new RuntimeException(e); } threadPool.shutdown(); long endTime = System.currentTimeMillis(); System.out.println("------costTime:" + (endTime - startTime) + "毫秒"); System.out.println(Thread.currentThread().getName() + "\t ------end"); } public static void m1() { // 三个任务,目前只有一个线程main来处理,请问耗时多少 long startTime = System.currentTimeMillis(); // 模拟三个任务穿行执行---耗时------costTime:1039毫秒 try { TimeUnit.MILLISECONDS.sleep(500); } catch (InterruptedException e) { throw new RuntimeException(e); } try { TimeUnit.MILLISECONDS.sleep(300); } catch (InterruptedException e) { throw new RuntimeException(e); } try { TimeUnit.MILLISECONDS.sleep(200); } catch (InterruptedException e) { throw new RuntimeException(e); } long endTime = System.currentTimeMillis(); System.out.println("------costTime:" + (endTime - startTime) + "毫秒"); System.out.println(Thread.currentThread().getName() + "\t ------end"); } }
-
缺点
-
get()方法容易阻塞
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"); // 暂停几秒钟---模拟任务耗时严重场景 TimeUnit.SECONDS.sleep(5); return "task over"; }); Thread t1 = new Thread(futureTask, "t1"); t1.start(); /* * 此处调用get方法回阻塞main线程去忙其他任务, * 因为只要调用main方法,主线程就会去等待futureTask结果 * 所以get()方法建议放到最后调用 */ // System.out.println(futureTask.get()); /* * 加入不愿意等待很长时间,希望过时不厚,可以自动离开 * 可以调用另一个get方法,传入过时时间 * 此时会抛出TimeoutException */ System.out.println(futureTask.get(3, TimeUnit.SECONDS)); System.out.println(Thread.currentThread().getName() + "\t 忙其他任务了"); } }
-
isDone()轮询
- 轮询的方式会耗费无谓的CPU资源,而且也不见得能及时地得到计算结果
- 如果想要异步获取结果,通常都会以轮询的方式去获取结果尽量不要阻塞
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"); // 暂停几秒钟---模拟任务耗时严重场景 TimeUnit.SECONDS.sleep(5); 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 { // 提示 TimeUnit.MILLISECONDS.sleep(500); System.out.println("正在处理中"); } } } }
-
结论:Future对于结果的获取不是很友好,只能通过阻塞或轮询地方式得到任务的结果。
-
2.4、想完成一些复杂的任务
- 对于简单的业务场景
对于简单的业务场景使用Future完全ok - 回调通知
应对Future的完成时间,完成了可以告诉我,也就是我们的回调通知,通过轮询的方式去判断任务是否完成这样非常占CPU并且代码也不优雅 - 创建异步任务
Future+线程池 - 多个任务前后依赖可以组合处理
想将多个一步任务的计算结果组合起来,后一个异步任务的计算结果需要前一个异步任务的值,将两个或多个异步计算合成一个异步计算,这几个异步计算互相独立,同时后面这个又依赖前一个处理的结果。 - 对计算速度选最快的
当Future集合中某个任务最快结束时,返回结果,返回第一名处理结果 - 解决
- 使用Future之前提供的那点API就囊中羞涩,处理起来不够优雅,这时候还是让CompeltableFuture以声明式的方式优雅的处理这些需求
- Future能干的,CompletableFuture都能干
3、CompletableFuture对Future的改进
3.1、CompletableFuture为什么出现?
- get()方法在Future计算完成之前会一直处在阻塞状态下,isDone()方法容易耗费CPU资源,对于真正的异步处理我们希望是可以通过传入回调函数,在Future结束时自动调用该回调函数,这样,我们就不用等待结果。
- 阻塞的方式和异步编程的设计理念相违背,而轮询的方式会耗费无谓的CPU资源。因此JDK8设计出CompletableFuture。
- CompletableFuture提供了一种观察者模式类似的机制,可以让任务执行完成后通知监听的一方。
3.2、CompletableFuture和CompletionStage源码分别介绍
-
类架构说明
CompletableFuture类,同时实现了Future接口和CompletionStage接口 -
接口CompletionStage
- CompletionStage代表异步计算过程中的某一个阶段,一个阶段完成以后可能会触发另外一个阶段
- 一个阶段的计算执行可以使一个Function,Consumer或者Runnable。比如:stage.thenApply(x -> square(x)).thenAccept(x -> System.out.print(x)).thenRun(() -> System.out.println())
- 一个阶段的执行可能是被单个阶段的完成触发也可能是由多个阶段一起触发
-
类CompletableFuture
- 在Java8中,CompletableFuture提供了非常强大的Future的扩展功能,可以帮助我们简化异步编程的复杂性,并且提供了函数式编程的能力,可以通过回调的方式处理计算结果,也提供了转换和组合CompletableFuture的方法
- 它能代表一个明确完成的Future,也有可能代表一个完成阶段(CompletionStage),它支持在计算完成以后触发一些函数或执行某些动作。
- 它实现了Future和CompletionStage接口
3.3、核心的四个静态方法,来创建一个异步任务
- 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);
}
-
上述Executor executor 参数说明
- 没有指定Executor的方法,直接使用默认的ForkJoinPool.commonPool()作为它的线程池执行异步代码
- 如果指定线程池,则使用我们自定义的或者特别指定的线程池执行异步代码
-
Code
- 使用runAsync不指定线程池创建异步任务(无返回值)
public class CompletableFutureBuildDemo { // 使用runAsync不指定线程池创建异步任务--无返回值 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) { throw new RuntimeException(e); } }); System.out.println(completableFuture.get()); } } /* * 结果: * ForkJoinPool.commonPool-worker-1 * null */
- 使用runAsync指定线程池创建异步任务(无返回值)
public class CompletableFutureBuildDemo { // 使用runAsync指定线程池创建异步任务--无返回值 public static void main(String[] args) throws ExecutionException, InterruptedException { ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3); CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(() -> { System.out.println(Thread.currentThread().getName()); // 模拟线程执行 try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { throw new RuntimeException(e); } }, fixedThreadPool); fixedThreadPool.shutdown(); System.out.println(completableFuture.get()); } } /* * 结果: * pool-1-thread-1 * null */
- 使用supplyAsync指定线程池创建异步任务(有返回值),supplyAsync不指定线程池时候,默认也是使用ForkJoinPool.commonPool线程池,此处不再过多介绍
public static void main(String[] args) throws ExecutionException, InterruptedException { // 使用supplyAsync指定线程池创建异步任务(有返回值) ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3); CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> { System.out.println(Thread.currentThread().getName()); // 模拟线程执行 try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { throw new RuntimeException(e); } return "hello supplyAsync"; }, fixedThreadPool); fixedThreadPool.shutdown(); System.out.println(completableFuture.get()); /* * 结果: * pool-1-thread-1 * hello supplyAsync */ }
-
Code值通用演示,减少阻塞和轮询
- 从Java8开始引入了CompletableFuture,它是Future的功能增强版,减少阻塞和轮询,可以传入回调对象,当异步任务完成或者发生异常时,自动调用回调对象的回调方法。
- code演示
public static void main(String[] args) throws ExecutionException, InterruptedException { // 默认线程池会自动关闭(会以守护线程方式创建),自定义线程池需要手动关闭(会以用户线程方式创建) ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3); CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> { System.out.println("是否守护线程" + Thread.currentThread().isDaemon()); System.out.println(Thread.currentThread().getName()); // 模拟线程执行 int result = ThreadLocalRandom.current().nextInt(10); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println("------1秒钟后出结果:" + result); return result; }, fixedThreadPool).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秒 // TimeUnit.SECONDS.sleep(3); // 如果是自定义线程池,则不需要,它是作为客户线程存在的,需要手动关闭 fixedThreadPool.shutdown(); }
-
CompletableFuture的优点
- 异步任务结束时,会自动回调某个对象的方法(whenComplete)
- 主线程设置好回调后,不再关心异步任务的执行,异步任务之间可以顺序执行(主线程可以去忙自己的事情了,异步任务交给线程池和CompletableFuture完成)
- 异步任务出错时,会自动回调某个对象的方法(exceptionally)
4、案例精讲-电商网站的比价需求
4.1、函数式编程
- Lambda表达式
- Stream流式调用
- Chain链式调用
- Java8函数式编程
// Runnable 无参数,无返回值
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
// Function<T, R>接受一个参数,并且有返回值
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
}
// Consumer接受一个参数,没有返回值
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}
// BiConsumer<T, U>接受两个参数(Bi,英文单词词根,代表两个的意思),没有返回值
@FunctionalInterface
public interface BiConsumer<T, U> {
void accept(T t, U u);
}
// Supplier没有参数,有一个返回值
@FunctionalInterface
public interface Supplier<T> {
T get();
}
// 链式编程
public class CompletableFutureMallDemo {
public static void main(String[] args) {
Student student = new Student();
student.setId(1);
student.setStudentName("z3");
student.setMajor("cs");
// 使用@Accessors(chain = true) 开启链式编程
student.setId(12).setStudentName("li4").setMajor("english");
}
}
@AllArgsConstructor
@NoArgsConstructor
@Data
@Accessors(chain = true)
class Student {
private Integer id;
private String studentName;
private String major;
}
4.2、join和get对比
join和get两个方法的功能是一样的,都能获取执行结果。
区别:
- get在编译时会抛出一样,代码中需要捕获或者向上抛出。
- join编译时不检验异常,运行时有异常再抛出
public class CompletableFutureMallDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
System.out.println("是否守护线程:" + Thread.currentThread().isDaemon());
return "hello 1234";
});
System.out.println(completableFuture.get());
}
}
public class CompletableFutureMallDemo {
public static void main(String[] args) {
CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
System.out.println("是否守护线程:" + Thread.currentThread().isDaemon());
return "hello 1234";
});
System.out.println(completableFuture.join());
}
}
4.3、业务需求说明
- 切记,功能->性能
- 电商网站比价需求分析
- 需求说明:
- 同一款产品,同时搜索出同款产品在各大电商平台的售价;
- 同一款产品,同时搜索出本产品在同一个电商平台下,各个入驻卖家售价是多少
- 输出返回:
出来结果希望是同款产品的在不同地方的价格清单列表,返回一个List- 《mysql》 in jd price is 88.05
- 《mysql》 in dangdang price is 86.11
- 《mysql》 in taobao price is 90.43
- 解决方案,比对同一个商品在各个平台上的价格,要求获得一个清单列表
- step by step 按部就班,查完京东查淘宝,查完淘宝查天猫…
- all in 万箭齐发,一口气多线程异步任务同时查询…
- 需求说明:
4.4、函数式编程实现比价案例实战功能
public class CompletableFutureMallDemo {
static List<NetMall> list = Arrays.asList(
new NetMall("jd"),
new NetMall("dangdang"),
new NetMall("taobao")
);
/**
* step by step
* @param list
* @param productName
* @return
*/
public static List<String> getPrice(List<NetMall> list, String productName) {
return list
.stream()
.map(netMall -> String.format(productName + " in %s price is %.2f",
netMall.getNetMallName(),
netMall.calcPrice(productName)))
.collect(Collectors.toList());
}
/**
* all in
* @param list
* @param productName
* @return
*/
public static List<String> getPriceByCompletableFuture(List<NetMall> list, String productName) {
return list.stream().map(netMall -> CompletableFuture.supplyAsync(() -> {
return String.format(productName + " in %s price is %.2f",
netMall.getNetMallName(),
netMall.calcPrice(productName));
})).collect(Collectors.toList()).stream().map(s -> s.join()).collect(Collectors.toList());
}
public static void main(String[] args) {
long startTime = System.currentTimeMillis();
// getPrice(list, "mysql").forEach(System.out::println);
getPriceByCompletableFuture(list, "mysql").forEach(System.out::println);
long endTime = System.currentTimeMillis();
System.out.println("------costTime:" + (endTime - startTime) + "毫秒");
}
}
class NetMall {
@Getter
private String netMallName;
public NetMall(String netMallName) {
this.netMallName = netMallName;
}
public double calcPrice(String productName) {
// 模拟查询数据
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return ThreadLocalRandom.current().nextDouble() * 2 + productName.charAt(0);
}
}
5、CompletableFuture常用方法
5.1、获得结果和触发计算
5.1.1、获取结果
- get()
- get(long timeout, TimeUnit unit)
- join()
- getNow(T valueIfAbsent)
没有计算完成的情况下,返回一个替代结果及计算完,返回计算完成的结果,没算完,返回设定的valueIfAbsent值
public class CompletableFutureAPIDemo {
public static void main(String[] args) {
CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return "abc";
});
// 若当前异步任务还没执行完成,则返回默认值“xxx”
System.out.println(completableFuture.getNow("xxx"));
}
}
5.1.2、主动触发计算
public boolean complete(T value)
是否打断get方法立即返回括号值
public class CompletableFutureAPIDemo {
public static void main(String[] args) {
CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return "abc";
});
// 若当前异步任务还没执行完成,打断get或join方法立即返回括号值
System.out.println(completableFuture.complete("completeValue") + "\t" + completableFuture.join());
}
}
5.2、对计算结果进行处理
5.2.1、thenApply
- 计算结果存在依赖关系,线程串行化,上一步的结果可以传递给下一步
- 由于存在依赖关系(当前步骤出错,不走下一步),当前步骤有异常的话就叫停。
public class CompletableFutureAPI2Demo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("111");
return 1;
}, fixedThreadPool).thenApply(f -> {
System.out.println("222");
return f + 2;
}).thenApply(f -> {
System.out.println("333");
return f + 3;
}).whenComplete((v, e) -> {
if (e == null) {
System.out.println("------计算结果:" + v);
}
}).exceptionally(e -> {
e.printStackTrace();
System.out.println("业务执行出现异常,异常原因:" + e.getCause() + "\t" + e.getMessage());
return null;
});
fixedThreadPool.shutdown();
System.out.println(Thread.currentThread().getName() + "\t主线程先去忙其它任务");
}
}
5.2.2、handle
- 计算结果存在依赖关系,线程串行化
- 有异常也可以往下一步走,根据带的异常参数可以进一步处理
public class CompletableFutureAPI2Demo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("111");
return 1;
}, fixedThreadPool).handle((f, e) -> {
int i = 10/0;
System.out.println("222");
return f + 2;
}).handle((f, e) -> {
System.out.println("333");
return f + 3;
}).whenComplete((v, e) -> {
if (e == null) {
System.out.println("------计算结果:" + v);
}
}).exceptionally(e -> {
e.printStackTrace();
System.out.println("业务执行出现异常,异常原因:" + e.getCause() + "\t" + e.getMessage());
return null;
});
fixedThreadPool.shutdown();
System.out.println(Thread.currentThread().getName() + "\t主线程先去忙其它任务");
}
public static void testThenApply() {
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("111");
return 1;
}, fixedThreadPool).thenApply(f -> {
System.out.println("222");
return f + 2;
}).thenApply(f -> {
System.out.println("333");
return f + 3;
}).whenComplete((v, e) -> {
if (e == null) {
System.out.println("------计算结果:" + v);
}
}).exceptionally(e -> {
e.printStackTrace();
System.out.println("业务执行出现异常,异常原因:" + e.getCause() + "\t" + e.getMessage());
return null;
});
fixedThreadPool.shutdown();
System.out.println(Thread.currentThread().getName() + "\t主线程先去忙其它任务");
}
/*
执行结果:
main 主线程先去忙其它任务
111
333
业务执行出现异常,异常原因:java.lang.NullPointerException java.lang.NullPointerException
*/
}
5.3、对计算结果进行消费
接收任务的处理结果,并消费处理,无返回结果
5.3.1、thenAccept
public class CompletableFutureAPI3Demo {
public static void main(String[] args) {
CompletableFuture.supplyAsync(() -> {
return 1;
}).thenApply(f -> {
return f + 2;
}).thenApply(f -> {
return f + 2;
}).thenAccept(r -> {
// 消费处理,无返回结果
System.out.println(r);
});
}
}
5.3.2、对比补充
任务之间的顺序执行
- thenRun
thenRun(Runnable action)–任务A执行完执行B,并且B不需要A的结果 - thenAccept
thenAccept(Consumer action)–任务A执行完执行B,B需要A的结果,但是任务B无返回值 - thenApply
thenApply(Function fn)–任务A执行完执行B,B需要A的结果,同时任务B有返回值
public class CompletableFutureAPI3Demo {
public static void main(String[] args) {
// 不需要入参,也没有返回结果
System.out.println(CompletableFuture.supplyAsync(() -> "resultA").thenRun(() -> {
}).join());
// 需要入参,但是没有返回结果
System.out.println(CompletableFuture.supplyAsync(() -> "resultA").thenAccept(System.out::println).join());
// 需要入参,也有返回值
System.out.println(CompletableFuture.supplyAsync(() -> "resultA").thenApply(f -> "return value").join());
}
}
5.3.3、CompletableFuture和线程池说明
- 以thenRun和thenRunAsync为例,有什么区别?
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) {
throw new RuntimeException(e);
}
System.out.println("1号任务" + "\t" + Thread.currentThread().getName());
return "abcd";
}, threadPool).thenRunAsync(() -> {
try {
TimeUnit.MILLISECONDS.sleep(20);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("2号任务" + "\t" + Thread.currentThread().getName());
}).thenRun(() -> {
try {
TimeUnit.MILLISECONDS.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("3号任务" + "\t" + Thread.currentThread().getName());
}).thenRun(() -> {
try {
TimeUnit.MILLISECONDS.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("4号任务" + "\t" + Thread.currentThread().getName());
});
System.out.println(completableFuture.get(2L, TimeUnit.SECONDS));
} catch (InterruptedException | TimeoutException | ExecutionException e) {
throw new RuntimeException(e);
} finally {
threadPool.shutdown();
}
/*
结果:
1号任务 pool-1-thread-1
2号任务 ForkJoinPool.commonPool-worker-25
3号任务 ForkJoinPool.commonPool-worker-25
4号任务 ForkJoinPool.commonPool-worker-25
null
*/
}
}
- 总结:
- 没有传入自定义线程池,都用默认线程池ForkJoinPool;
- 传入了一个自定义线程池
如果执行第一个任务的时候,传入了一个自定义线程池- 调用thenRun方法执行第二个任务时,则第二个任务和第一个任务是共用同一个线程池
- 调用thenRunAsync执行第二个任务时,则第一个任务使用的时自定义线程池,第二个任务使用的时ForkJoin线程池
- 调用thenRunAsync执行第二个任务并且也指定自定义线程池,则第一个任务和第二个任务都会使用的时自定义线程池
- 备注
有可能处理太快,系统优化切换原则,直接使用main线程处理。其他如thenAccept和thenAcceptAsync,thenApply和thenApplyAsync等,它们之间的区别也是同理。
- 源码微解读
// 可以看到thenRun的uniRunStage方法第一个参数传入的时null,而thenRunAsync中传入了一个异步线程池
public CompletableFuture<Void> thenRun(Runnable action) {
return uniRunStage(null, action);
}
public CompletableFuture<Void> thenRunAsync(Runnable action) {
return uniRunStage(asyncPool, action);
}
// 在asyncPool 中有一个三目运算,计算cpu核数是否大于1,如果是就用默认的ForkJoinPool线程池
private static final boolean useCommonPool =
(ForkJoinPool.getCommonPoolParallelism() > 1);
private static final Executor asyncPool = useCommonPool ?
ForkJoinPool.commonPool() : new ThreadPerTaskExecutor();
5.4、对计算速度进行选用
谁快用谁
- applyToEither
public class CompletableFutureFastDemo {
public static void main(String[] args) {
CompletableFuture<String> playerA = CompletableFuture.supplyAsync(() -> {
System.out.println("A come in");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return "playerA";
});
CompletableFuture<String> playerB = CompletableFuture.supplyAsync(() -> {
System.out.println("B come in");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return "playerB";
});
System.out.println(playerA.applyToEither(playerB, f -> f + " is winner").join());
}
}
5.5、对计算结果进行合并
- thenCombine
两个CompletionStage任务都完成后,最终能把两个任务的结果一起交给thenCombine来处理;先完成的先等着,等待其他分支任务
public class CompletableFutureCombineDemo {
public static void main(String[] args) {
CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + "\t ---启动");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return 10;
});
CompletableFuture<Integer> completableFuture2 = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + "\t ---启动");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return 20;
});
CompletableFuture<Integer> result = completableFuture.thenCombine(completableFuture2, (x, y) -> {
System.out.println("-----开始两个结果合并");
return x + y;
});
System.out.println(result.join());
}
}