文章目录
1、概述
- 咱们都知道可以通过继承Thread类或者实现 Runnable 接口两种方式实现多线程。但是有时候我们希望得到多线程异步任务执行后的结果,也就是异步任务执行后有返回值,Thread 和 Runnable 是不能实现的。
- 当我们需要返回值的时候怎么办呢? Java 1.5 推出的 Callable 和 Future 接口就解决了这个问题。但是因为 Future 有几个局限,由于这几个局限,在Java1.8就推出了加强版的Future类:CompletableFuture。
- 本篇文章我们通过实际需求、实例代码分析 Future 缺陷讲解 CompletableFuture 的设计原理。
2、Future使用
场景如下
- 老板正在开会,开会过程中发现少一份材料,通知秘书去整理,在秘书整理过程中老板这边还在继续开会,秘书整理完以后将材料给到老板手中。
- 需求分析:
- 老板开会是主线程,不能中断
- 秘书就是异步任务
- 秘书执行完任务需要将结果返回给老板这个主线程手中
- 咱们看看通过 Future 实现此需求有什么局限,然后再通过CompletableFuture实现此需求看看是否更好。
- Future接口(实现类:FutureTask)定义了操作异步任务执行的一些方法:如获取异步任务执行结果、取消任务的执行结果、判断任务是否被取消、判断任务执行是否完成等。
实现老板开会,秘书整理材料需求方式一代码:
package com.lc;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
/**
1. 老板开会Future实现
2. 3. @author liuchao
4. @date 2023/4/5
*/
public class BossMeeting {
/**
* 主线程为老板正在开会
*
* @param args
*/
public static void main(String[] args) {
System.out.println("老板开会start");
ExecutorService executorService = Executors.newFixedThreadPool(1);
FutureTask<String> secretaryFuture = new FutureTask<>(() -> {
Thread.sleep(1000);
return "老板需要的材料";
});
//老板发现缺少材料,提交异步任务(找秘书)
executorService.submit(secretaryFuture);
/**
* 方法1
* 局限:导致线程堵塞
*/
try {
//获取秘书搜集的材料 (堵塞线程)
String material = secretaryFuture.get();
System.out.println("秘书搜集到的材料:" + material);
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
/**
* 方法2
* 通过while轮询方式会消耗cpu
*/
while (true) {
if (secretaryFuture.isDone()) {
try {
//获取秘书搜集的材料 (堵塞线程)
String material = secretaryFuture.get();
System.out.println("秘书搜集到的材料:" + material);
break;
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
} else {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
System.out.println("老板开会end");
}
}
实例代码提供了两种方式获取秘书搜集到的材料,都是有局限性并且堵塞了主线程。
通过现实需求分析,老板开会能一直等着秘书将材料整理完再继续吗,显然是不行的。
现实情况是秘书(异步任务)执行完任务后,主动告知老板(主线程)。
Future使用局限性汇总:
- Future的get方法会导致主线程阻塞
- 轮询获取结果会消耗cpu资源
- 多个Future任务不能按照顺序执行
- Future Api无异常处理
3、CompletableFuture实现
CompletableFuture提供了一种观察者模式类似的机制,可以让任务执行完成后通知监听的一方。
首先看看CompletableFuture的类图关系,CompletableFuture实现了Future和CompletionStage接口,因此看来CompletableFuture具有Future和CompletionStage的特性。
public class CompletableFuture<T> implements Future<T>, CompletionStage<T> {
CompletionStage接口拥有的API
咱们看看通过CompletableFuture实现的老板开会需求代码实例如下:
package com.lc;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 老板开会Future实现
*
* @author liuchao
* @date 2023/4/5
*/
public class BossMeeting {
/**
* 主线程为老板正在开会
*
* @param args
*/
public static void main(String[] args) {
System.out.println("老板开会start");
ExecutorService executorService = Executors.newFixedThreadPool(1);
try {
CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return "秘书搜集完材料";
//结束返回
}, executorService).whenComplete((v, e) -> {
//无异常说明 执行成功
if (e == null) {
System.out.println("秘书搜集到的材料:" + v);
}
//异常处理
}).exceptionally(e -> {
e.printStackTrace();
System.out.println("执行异常:" + e.getCause());
return null;
});
System.out.println("老板继续开会");
try {
//模拟老板继续开会3秒钟
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("老板开会end");
} finally {
executorService.shutdown();
}
}
}
执行结果:发现没有任何堵塞,任务提交主线程继续执行,异步任务执行完成主动告知主线程
- 老板开会start
- 老板继续开会
- 秘书搜集到的材料:秘书搜集完材料
- 老板开会end
4、CompletableFuture Api详解
4.1、CompletableFuture创建方式
官方推荐使用CompletableFuture提供的静态方法创建CompletableFuture实例,以下是提供的静态方法:
// 无返回值 使用ForkJoinPool线程池
public static CompletableFuture<Void> runAsync(Runnable runnable)
// 无返回值 可以自定义线程池
public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor)
// 有返回值 使用ForkJoinPool线程池
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
// 有返回值 可以自定义线程池
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)
- supply开头:这种方法,可以返回异步线程执行之后的结果。
- run开头:这种不会返回结果,就只是执行线程任务。
如果你想异步运行一些后台任务并且不想从任务中返回任何东西,那么你可以使用run开头的
实例:
package com.lc;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @author liuchao
* @date 2023/4/5
*/
public class Test {
public static void main(String[] args) {
//无返回结果
CompletableFuture.runAsync(() -> {
System.out.println("无返回值线程:" + Thread.currentThread().getName());
System.out.println("执行异步任务,无返回结果");
});
//无返回值 自定义线程
ExecutorService executors = Executors.newFixedThreadPool(2);
CompletableFuture.runAsync(() -> {
System.out.println("无返回值,自定义线程:" + Thread.currentThread().getName());
System.out.println("执行异步任务,无返回自定义线程结果");
}, executors);
//有返回结果
CompletableFuture.supplyAsync(() -> {
System.out