- 一、Fork/Join
- 1. 描述
- 2. 使用
- 二、Future
- 1. 描述
- 2. 使用
- 三、CompletableFuture API介绍
- 1. 描述
- 2. CompletionStage
- 3. CompletableFuture 4个核心静态方法
- (1)runAsync(Runnable runnable)
- (2)runAsync(Runnable runnable, Executor executor)
- (3)supplyAsync(Supplier supplier)
- (4)supplyAsync(Supplier supplier, Executor executor)
- 4. CompletableFuture 常用方法
- (1) 获得结果和触发计算类型的方法
- get()
- get(long timeout, TimeUnit unit)
- join()
- getNow(T valueIfAbsent)
- (2) 对计算结果进行处理类型的方法
- thenApply
- handle
- thenApply 与 thenCompose 对比
- whenComplete
- exceptionally、whenComplete、handle 对比
- (3) 对计算结果进行消费类型的方法
- thenAccept
- thenRun
- thenRun、thenAccept、thenApply 对比
- thenRun 与 thenRunAsync 对比
- (4) 对计算速度进行选用类型的方法
- applyToEither
- applyToEither、acceptEither、runAfterEither 对比
- (5) 对计算结果进行合并类型的方法
- thenCombine
- thenCombine、thenAcceptBoth、runAfterBoth 对比
- 5. CompletableFuture 性能
- 分析 CompletableFuture 完成异步任务
- 四、CompletableFuture 使用背景
- 1. 引入CompletableFuture的背景
- 2. 并行加载的实现方式
- (1)同步模型
- (2)NIO异步模型
- 3. 选择CompletableFuture的原因
- 4. CompletableFuture解决的问题
- 5. CompletableFuture的使用
- (1)零依赖:CompletableFuture的创建
- (2)一元依赖:依赖一个CompletableFuture
- (3)二元依赖:依赖两个CompletableFuture
- (4)多元依赖:依赖多个CompletableFuture
- 五、CompletableFuture 原理
- 1. CompletableFuture 基本结构
- 2. CompletableFuture的设计思想
- 3. CompletableFuture 整体流程
- (1) 一元依赖
- (2) 二元依赖
- (3) 多元依赖
- 六、CompletableFuture 实践总结
- 1. 线程阻塞问题
- (1) 代码执行在哪个线程上?
- 2. 线程池须知
- (1)异步回调要传线程池
- (2) 线程池循环引用会导致死锁
- (3) 异步RPC调用注意不要阻塞IO线程池
- 3. 异常处理
- 4. 自定义 CompletableFuture 工具方法
- (1)自定义函数
- (2)CompletableFuture处理工具类
- (3)异常提取工具类
- (4)打印日志
- (5)日志处理实现类
- (6)打印日志方式
- (7)异常情况返回默认值
- (8)默认返回值应用示例
- Fork/Join 是 JDK 1.7 加入的新的线程池实现,它体现的是一种分治思想,适用于能够进行任务拆分的 cpu 密集型运算
- 所谓的任务拆分,是将一个大任务拆分为算法上相同的小任务,直至不能拆分可以直接求解。跟递归相关的一些计算,如归并排序、斐波那契数列、都可以用分治思想进行求解
- Fork/Join 在分治的基础上加入了多线程,可以把每个任务的分解和合并交给不同的线程来完成,进一步提升了运算效率
- Fork/Join 默认会创建与 cpu 核心数大小相同的线程池
- 提交给 Fork/Join 线程池的任务需要继承 RecursiveTask(有返回值)或 RecursiveAction(没有返回值)
计算对 1~n 之间的整数求和的任务
@Slf4j(topic = "c.TestForkJoin2")
public class TestForkJoin2 {
public static void main(String[] args) {
// 分配4个线程
ForkJoinPool pool = new ForkJoinPool(4);
System.out.println("结果:" + pool.invoke(new MyTask(5)));
}
}
// 1~n 之间整数的和
@Slf4j(topic = "c.MyTask")
class MyTask extends RecursiveTask {
private final int n;
public MyTask(int n) {
this.n = n;
}
@Override
public String toString() {
return "{" + n + '}';
}
@Override
protected Integer compute() {
// 如果 n 已经为 1,可以求得结果了
// 终止条件
if (n == 1) {
log.debug("join({}) ", n);
return n;
}
// fork:将任务进行拆分
AddTask1 t1 = new AddTask1(n - 1);
t1.fork();
log.debug("fork({}) + {}", n, t1);
// 合并(join)结果
int result = n + t1.join();
log.debug("join({}) + {} = {}", n, t1, result);
return result;
}
}
11:26:43.554 c.AddTask [ForkJoinPool-1-worker-3] - fork() 3 + {2}
11:26:43.554 c.AddTask [ForkJoinPool-1-worker-2] - fork() 4 + {3}
11:26:43.554 c.AddTask [ForkJoinPool-1-worker-0] - fork() 2 + {1}
11:26:43.554 c.MyTask [ForkJoinPool-1-worker-1] - fork(5) + {4}
11:26:43.560 c.AddTask [ForkJoinPool-1-worker-2] - join() 1
11:26:43.560 c.AddTask [ForkJoinPool-1-worker-0] - join() 2 + {1} = 3
11:26:43.560 c.AddTask [ForkJoinPool-1-worker-3] - join() 3 + {2} = 6
11:26:43.560 c.AddTask [ForkJoinPool-1-worker-2] - join() 4 + {3} = 10
11:26:43.560 c.MyTask [ForkJoinPool-1-worker-1] - join(5) + {4} = 15
结果:15
进一步优化:拆分成两部分
public class TestForkJoin {
public static void main(String[] args) {
ForkJoinPool pool = new ForkJoinPool(4);
System.out.println("结果:" + pool.invoke(new AddTask3(1, 5)));
}
}
@Slf4j(topic = "c.AddTask")
class AddTask3 extends RecursiveTask {
int begin;
int end;
public AddTask3(int begin, int end) {
this.begin = begin;
this.end = end;
}
@Override
public String toString() {
return "{" + begin + "," + end + '}';
}
@Override
protected Integer compute() {
// 当计算到最后一个数时,终止
if (begin == end) {
log.debug("join({}) ", begin);
return begin;
}
// 只剩最后两个相邻数时,也可以终止拆分
if (end - begin == 1) {
log.debug("join({}) + {} = {}", begin, end, end + begin);
return end + begin;
}
// 分两部分拆分
int mid = (end + begin) / 2;
AddTask3 t1 = new AddTask3(begin, mid);
t1.fork();
AddTask3 t2 = new AddTask3(mid + 1, end);
t2.fork();
log.debug("fork({}) + {} = ?", t1, t2);
int result = t1.join() + t2.join();
log.debug("join({}) + {} = {}", t1, t2, result);
return result;
}
}
11:35:38.711 c.AddTask [ForkJoinPool-1-worker-1] - fork({1,3}) + {4,5} = ?
11:35:38.711 c.AddTask [ForkJoinPool-1-worker-3] - join(4) + 5 = 9
11:35:38.717 c.AddTask [ForkJoinPool-1-worker-1] - join(3)
11:35:38.711 c.AddTask [ForkJoinPool-1-worker-2] - fork({1,2}) + {3,3} = ?
11:35:38.711 c.AddTask [ForkJoinPool-1-worker-0] - join(1) + 2 = 3
11:35:38.717 c.AddTask [ForkJoinPool-1-worker-2] - join({1,2}) + {3,3} = 6
11:35:38.718 c.AddTask [ForkJoinPool-1-worker-1] - join({1,3}) + {4,5} = 15
结果:15
二、Future
1. 描述
- Future 表示异步计算的结果。
- 提供了检查计算是否完成、等待其完成以及检索计算结果的方法。
- 结果只能在计算完成后使用方法 get 检索,必要时阻塞,直到它准备好。
- 取消是通过取消方法执行的。提供了额外的方法来确定任务是正常完成还是被取消。一旦计算完成,就不能取消计算。
- 如果想使用 Future 来取消可取消性但不提供可用的结果,您可以声明 Future> 形式的类型并返回 null 作为底层任务的结果。
Future源码:
public interface Future2. 使用{ boolean cancel(boolean mayInterruptIfRunning); boolean isCancelled(); boolean isDone(); V get() throws InterruptedException, ExecutionException; V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException; }
- 如果主线程需要执行一个很耗时的任务,可以通过Future把这个任务放到异步线程中执行
- 主线程继续处理其他任务或者先行结束,可以通过get获取计算结果
- 一句话总结:Future接口可以为主线程开一个异步线程,专门为主线程执行耗时任务
Future接口的实现类需要满足3个特点:多线程、有返回值、执行异步任务
FutureTask实现类可以满足以上3个特点
- FutureTask 能够接收 Callable 类型的参数,用来处理有返回结果的情况。
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 创建任务对象
FutureTask futureTask = new FutureTask(() -> {
log.debug("callable执行任务");
Thread.sleep(1000);
return 100;
});
// 参数1 是任务对象; 参数2 是线程名
Thread t1 = new Thread(futureTask, "t1");
t1.start();
// 主线程阻塞,同步等待 futureTask 执行完毕的结果
Integer result = futureTask.get();
// 1秒后获取到结果
log.debug("获取FutureTask结果:{}", result);
}
22:36:52.884 c.FutureTaskThread [t1] - callable执行任务 22:36:53.899 c.FutureTaskThread [main] - 获取FutureTask结果:100 Process finished with exit code 0
Callable源码
// Callable是函数式接口,有返回结果,能抛出异常 @FunctionalInterface public interface Callable{ V call() throws Exception; }
FutureTask源码
// 1.FutureTask 能够接收 Callable 类型的参数,用来处理有返回结果的情况,get方法可以获取返回结果 // 2.FutureTask类是 Runnable接口 的扩展,FutureTask 既有Runnable中的run方法,又有Future中的get方法 public class FutureTaskimplements RunnableFuture { public FutureTask(Callable callable) { if (callable == null) throw new NullPointerException(); this.callable = callable; this.state = NEW; // ensure visibility of callable } } // 接口可以多继承 public interface RunnableFuture extends Runnable, Future { void run(); }
- 优点:FutureTask配合线程池执行异步任务,可以提升执行效率
@Slf4j(topic = "c.TestFutureTask")
public class TestFutureTask {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService pool = Executors.newFixedThreadPool(3);
long start = System.currentTimeMillis();
FutureTask task1 = new FutureTask<>(() -> {
Thread.sleep(1000);
return 1;
});
pool.submit(task1);
FutureTask task2 = new FutureTask<>(() -> {
Thread.sleep(2000);
return 2;
});
pool.submit(task2);
FutureTask task3 = new FutureTask<>(() -> {
Thread.sleep(3000);
return 3;
});
pool.submit(task3);
log.debug("{}", task1.get());
log.debug("{}", task2.get());
log.debug("{}", task3.get());
long end = System.currentTimeMillis();
log.debug("总耗时:{}毫秒", end - start);
pool.shutdown();
}
}
16:08:38.938 c.TestFutureTask [main] - 1 16:08:39.931 c.TestFutureTask [main] - 2 16:08:40.934 c.TestFutureTask [main] - 3 16:08:40.934 c.TestFutureTask [main] - 总耗时:3065毫秒
- isDone()轮询获取异步任务结果
@Slf4j(topic = "c.TestFutureTask")
public class TestFutureTask {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService pool = Executors.newFixedThreadPool(1);
long start = System.currentTimeMillis();
FutureTask task1 = new FutureTask<>(() -> {
Thread.sleep(3000);
return 1;
});
pool.submit(task1);
while (true) {
if (task1.isDone()) {
log.debug("{}", task1.get());
break;
} else {
Thread.sleep(500);
log.debug("正在处理中,请等到。。。。");
}
}
long end = System.currentTimeMillis();
log.debug("总耗时:{}毫秒", end - start);
pool.shutdown();
}
}
16:24:00.121 c.TestFutureTask [main] - 正在处理中,请等到。。。。 16:24:00.632 c.TestFutureTask [main] - 正在处理中,请等到。。。。 16:24:01.139 c.TestFutureTask [main] - 正在处理中,请等到。。。。 16:24:01.644 c.TestFutureTask [main] - 正在处理中,请等到。。。。 16:24:02.150 c.TestFutureTask [main] - 正在处理中,请等到。。。。 16:24:02.666 c.TestFutureTask [main] - 正在处理中,请等到。。。。 16:24:02.666 c.TestFutureTask [main] - 1 16:24:02.667 c.TestFutureTask [main] - 总耗时:3101毫秒
FutureTask缺点:
- get()阻塞,调用get方法计算结果,如果计算没有完成容易导致程序阻塞
- isDone()轮询会耗费无畏的CPU资源,并且也不能及时得到计算结果
- CompletableFuture实现了两个接口(如上图所示):Future、CompletionStage。
- Future表示异步计算的结果
- CompletionStage用于表示异步执行过程中的一个步骤(Stage),这个步骤可能是由另外一个CompletionStage触发的,随着当前步骤的完成,也可能会触发其他一系列CompletionStage的执行。
- 从而我们可以根据实际业务对这些步骤进行多样化的编排组合,CompletionStage接口正是定义了这样的能力,我们可以通过其提供的thenAppy、thenCompose等函数式编程方法来组合编排这些步骤。
// CompletableFuture 实现了Future、CompletionStage两个接口 public class CompletableFutureimplements Future , CompletionStage
以下是对源码描述的翻译(需要细品):
- CompletableFuture是一个可以显式完成的 Future(可以设置其值和状态),并且可以用作 CompletionStage,支持在其完成时触发相关函数和操作。
- 当两个或更多线程尝试完成、完成异常或取消 CompletableFuture 时,只有其中一个成功。
- 除了直接操作状态和结果的这些相关方法之外,CompletableFuture 还使用以下策略实现了 CompletionStage接口:
- 为非异步方法的依赖完成提供的操作,可以由完成当前 CompletableFuture 的线程执行,也可以由完成方法的任何其他调用者执行(例如:main线程空闲后也可以执行CompletableFuture任务)。
- 所有没有传入 Executor 参数的异步方法,都使用 ForkJoinPool.commonPool() 执行(除非它不支持至少两个并行级别,在这种情况下,会创建一个新线程来运行每个任务)。为了简化监控、调试和跟踪,所有生成的异步任务都是标记接口 CompletableFuture.AsynchronousCompletionTask 的实例。
- 所有 CompletionStage 方法都是独立于其他公共方法实现的,因此一个方法的行为不会受到子类中其他方法的覆盖的影响。
- CompletableFuture 还使用以下策略实现 Future接口:
- 由于(与 FutureTask 不同)此类无法直接控制导致其完成的计算,因此取消被视为另一种形式的异常完成。方法 cancel 与 completeExceptionally(new CancellationException()) 具有相同的效果。方法 isCompletedExceptionally 可用于确定 CompletableFuture 是否以任何异常方式完成。
- 如果出现 CompletionException 异常完成,get() 和 get(long, TimeUnit) 方法会抛出 ExecutionException,原因与对应的 CompletionException 中的原因相同。为了在大多数情况下简化使用,此类还定义了 join() 和 getNow 方法,它们在这些情况下直接抛出 CompletionException。
由于 FutureTask 的2个缺点,JDK8设计出了CompletableFuture,CompletableFuture 提供了一种类似观察者模式的机制, 可以让异步任务完成后回调通知监听的一方,可以避免阻塞和轮询 CompletableFuture 优点: 异步任务或异常,会自动回调某个对象的方法 主线程设置好回调后,不再关心异步任务的执行,异步任务之间也可以顺序执行2. CompletionStage
以下是对源码描述的翻译(需要细品):
- CompletionStage可能是异步计算的一个阶段,它在另一个 CompletionStage 完成时执行一个操作或计算一个值。一个阶段在其计算终止时完成,但这可能反过来触发其他相关阶段。此接口中定义的功能仅采用几种基本形式,可扩展为更大的方法集以捕获一系列使用风格:
- 阶段执行的计算可以表示为 Function、Consumer 或 Runnable(分别使用名称包括 apply、accept 或 run 的方法),具体取决于它是否需要参数或产生结果。例如 stage.thenApply(x -> square(x)).thenAccept(x -> System.out.print(x)).thenRun(() -> System.out.println())。另一种形式(compose)应用阶段本身的功能,而不是它们的结果。
- 一个阶段的执行可以由单个阶段的完成触发,也可以由两个阶段的完成触发,或者两个阶段中的任一个触发。使用带有前缀 then 的方法来排列单个阶段的依赖关系。由两个阶段完成触发的那些可以使用相应命名的方法组合它们的结果或效果。由两个阶段中的任何一个触发的那些不保证哪些结果或效果用于从属阶段的计算。
- 两种方法形式支持处理触发阶段是正常完成还是异常完成:方法 whenComplete 允许注入动作而不管结果如何,否则在完成时保留结果。方法句柄还允许阶段计算替换结果,该结果可以允许其他相关阶段进行进一步处理。在所有其他情况下,如果一个阶段的计算因(未经检查的)异常或错误而突然终止,则所有需要其完成的相关阶段也会异常完成,并且 CompletionException 将异常作为其原因。如果一个阶段依赖于两个阶段,并且都异常完成,那么 CompletionException 可能对应于这些异常中的任何一个。如果一个阶段依赖于其他两个阶段中的任何一个,并且其中只有一个异常完成,则无法保证依赖阶段是正常完成还是异常完成。在方法 whenComplete 的情况下,当提供的操作本身遇到异常时,如果尚未异常完成,则阶段异常完成此异常。
- 所有方法都遵循上述触发、执行和异常完成规范(在单个方法规范中不再重复)。此外,虽然用于为接受它们的方法传递完成结果(即,对于类型 T 的参数)的参数可能为 null,但为任何其他参数传递 null 值将导致引发 NullPointerException。
- 该接口未定义初始创建、正常或异常强制完成、探测完成状态或结果或等待阶段完成的方法。 CompletionStage 的实现可以酌情提供实现这种效果的方法。方法 toCompletableFuture 通过提供一个通用的转换类型来实现此接口的不同实现之间的互操作性。
runAsync 执行的任务无返回值, public static CompletableFuturerunAsync(Runnable runnable) public static CompletableFuture runAsync(Runnable runnable, Executor executor) supplyAsync 执行的任务有返回值 public static CompletableFuture supplyAsync(Supplier supplier) public static CompletableFuture supplyAsync(Supplier supplier, Executor executor) 以上传入 Executor 可以使用我们指定的线程池,不传则使用默认的 ForkJoinPool.commonPool()线程池
CompletableFuture 源码:
public class CompletableFutureimplements Future , CompletionStage { // 默认线程池 —— ForkJoinPool.commonPool() 除非它不支持并行。 private static final Executor asyncPool = useCommonPool ? ForkJoinPool.commonPool() : new ThreadPerTaskExecutor(); // 如果 ForkJoinPool.commonPool() 不支持并行性,则回退 static final class ThreadPerTaskExecutor implements Executor { public void execute(Runnable r) { new Thread(r).start(); } } // 返回一个新的 CompletableFuture,它在运行给定操作后由 ForkJoinPool.commonPool() 中运行的任务异步完成。 public static CompletableFuture runAsync(Runnable runnable) { return asyncRunStage(asyncPool, runnable); } // 返回一个新的 CompletableFuture,它由在 ForkJoinPool.commonPool() 中运行的任务异步完成,调用者可以获取返回值。 public static CompletableFuture supplyAsync(Supplier supplier) { return asyncSupplyStage(asyncPool, supplier); } }
测试 1:
@Slf4j(topic = "c.TestsSupplyAsync")
public class TestsSupplyAsync {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService pool = Executors.newFixedThreadPool(3);
CompletableFuture supplyAsync = CompletableFuture.supplyAsync(() -> {
log.debug("线程名:{}", Thread.currentThread().getName());
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "hello supplyAsync";
}, pool);
log.debug("{}", supplyAsync.get());
// 也可以使用join,使用join可以不用声明异常
// log.debug("{}", supplyAsync.join());
pool.shutdown();
}
}
17:52:23.694 c.TestsSupplyAsync [pool-1-thread-1] - 线程名:pool-1-thread-1 17:52:24.706 c.TestsSupplyAsync [main] - hello supplyAsync
测试 2:
需求:
- 同时搜索出同款产品在各大平台的售价
- 输出格式:List< String>
- 《Java》in JD price is ***
- 《Java》in DangDang price is ***
- 《Java》in Taobao price is ***
public class TestCompletableFuture {
static List list = Arrays.asList(
new Mall("JD"),
new Mall("DangDang"),
new Mall("TaoBao")
);
public static void main(String[] args) {
long start = System.currentTimeMillis();
List price = getPrice(list, "java");
price.forEach(System.out::println);
long end = System.currentTimeMillis();
System.out.println("耗时:" + (end - start) + "毫秒");
}
// List -> List> -> List
public static List getPrice(List mallList, String bookName) {
return mallList.stream()
.map(mall -> CompletableFuture.supplyAsync(() ->
String.format(bookName + " in %s price is %.2f",
mall.getMallName(),
mall.getBookPrice(bookName))))
.collect(Collectors.toList())
.stream()
.map(CompletableFuture::join)
.collect(Collectors.toList());
}
}
class Mall {
@Getter
private final String mallName;
public Mall(String mallName) {
this.mallName = mallName;
}
public double getBookPrice(String bookName){
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 随机产生价格
return ThreadLocalRandom.current().nextDouble() * 2 + bookName.charAt(0);
}
}
java in JD price is 106.31 java in DangDang price is 106.35 java in TaoBao price is 107.81 耗时:1125毫秒4. CompletableFuture 常用方法 (1) 获得结果和触发计算类型的方法
- 获得结果类型的方法
// 等待任务完成,并返回结果 public T get() throws InterruptedException, ExecutionException // 带超时的等待,并返回结果,超时则抛出异常 public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException // 等待任务完成,并返回结果,不用声明异常 (编译时不会检查异常) // 出现异常时,抛出 CompletionException 异常 public T join() // 如果任务完成则返回结果值(或抛出任何遇到的异常),否则返回给定的参数值 valueIfAbsent // 不会阻塞 public T getNow(T valueIfAbsent)
测试 getNow()
public static void main(String[] args) {
CompletableFuture completableFuture = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "测试getNow方法";
});
System.out.println("结果:" + completableFuture.getNow("这是任务没完成返回的指定值"));
}
结果:这是任务没完成返回的指定值
- 主动触发计算的方法
// 如果尚未完成,则将 get() 和相关方法返回的值设置为给定值 // 表示是否打断get()、join()等方法,立即返回指定值 value public boolean complete(T value)
测试 complete():
public static void main(String[] args) throws InterruptedException, ExecutionException {
CompletableFuture completableFuture = CompletableFuture.supplyAsync(() -> {
try {
// 执行 2s
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "测试getNow方法";
});
// 等 1s
TimeUnit.SECONDS.sleep(1);
// 是否打断get()等方法
boolean flag = completableFuture.complete("这是任务没完成返回的指定值");
String value = completableFuture.get();
System.out.println("是否打断:" + flag + ", 结果:" + value);
}
是否打断:true, 结果:这是任务没完成返回的指定值(2) 对计算结果进行处理类型的方法 thenApply
- 计算结果存在依赖关系,多个线程之间是串行化的
- 当前阶段出现异常,将不会执行后面阶段
- 返回一个新的 CompletionStage,当此阶段正常完成时,将使用此阶段的结果作为下一个阶段的参数
public CompletionStage thenApply(Function super T,? extends U> fn);
测试正常情况:
@Slf4j(topic = "c.TestThenApply")
public class TestThenApply {
public static void main(String[] args) {
ExecutorService pool = Executors.newFixedThreadPool(3);
CompletableFuture.supplyAsync(() -> {
log.debug("第一阶段");
return 1;
}, pool).thenApply((v) -> {
log.debug("第二阶段");
return v + 2;
}).thenApply((v) -> {
log.debug("第三阶段");
return v + 3;
}).whenComplete((v, e) -> {
log.debug("结果:" + v);
}).exceptionally(e -> {
log.debug("出现异常:" + e.getMessage());
return null;
});
pool.shutdown();
}
}
22:45:39.496 c.TestThenApply [pool-1-thread-1] - 第一阶段 22:45:39.499 c.TestThenApply [pool-1-thread-1] - 第二阶段 22:45:39.499 c.TestThenApply [pool-1-thread-1] - 第三阶段 22:45:39.500 c.TestThenApply [pool-1-thread-1] - 结果:6
测试异常情况:
@Slf4j(topic = "c.TestThenApply")
public class TestThenApply {
public static void main(String[] args) {
ExecutorService pool = Executors.newFixedThreadPool(3);
CompletableFuture.supplyAsync(() -> {
log.debug("第一阶段");
return 1;
}, pool).thenApply((v) -> {
int i = 10 / 0;
log.debug("第二阶段");
return v + 2;
}).thenApply((v) -> {
log.debug("第三阶段");
return v + 3;
}).whenComplete((v, e) -> {
log.debug("结果:" + v);
}).exceptionally(e -> {
log.debug("出现异常:" + e.getMessage());
return null;
});
pool.shutdown();
}
}
22:46:05.153 c.TestThenApply [pool-1-thread-1] - 第一阶段 22:46:05.158 c.TestThenApply [pool-1-thread-1] - 结果:null 22:46:05.158 c.TestThenApply [pool-1-thread-1] - 出现异常:java.lang.ArithmeticException: / by zerohandle
- 计算结果存在依赖关系,多个线程之间是串行化的
- 当前阶段出现异常,将会执行后面阶段,可以根据异常做进一步处理
- 返回一个新的 CompletionStage,当此阶段正常或异常完成时,将使用此阶段的结果作为下一个阶段的参数
public CompletionStage handle(BiFunction super T, Throwable, ? extends U> fn);
测试正常情况:
@Slf4j(topic = "c.TestHandle")
public class TestHandle {
public static void main(String[] args) {
ExecutorService pool = Executors.newFixedThreadPool(3);
CompletableFuture.supplyAsync(() -> {
log.debug("第一阶段");
return 1;
}, pool).handle((v, e) -> {
log.debug("第二阶段");
return v + 2;
}).handle((v, e) -> {
log.debug("第三阶段");
if (e != null) {
log.debug("捕获上一阶段的异常:" + e.getMessage());
}
return v + 3;
}).whenComplete((v, e) -> {
log.debug("结果:" + v);
}).exceptionally(e -> {
log.debug("出现异常:" + e.getMessage());
return null;
});
pool.shutdown();
}
}
22:39:52.103 c.TestHandle [pool-1-thread-1] - 第一阶段 22:39:52.107 c.TestHandle [pool-1-thread-1] - 第二阶段 22:39:52.107 c.TestHandle [pool-1-thread-1] - 第三阶段 22:39:52.107 c.TestHandle [pool-1-thread-1] - 结果:6
测试异常情况:
@Slf4j(topic = "c.TestHandle")
public class TestHandle {
public static void main(String[] args) {
ExecutorService pool = Executors.newFixedThreadPool(3);
CompletableFuture.supplyAsync(() -> {
log.debug("第一阶段");
return 1;
}, pool).handle((v, e) -> {
int i = 10 / 0;
log.debug("第二阶段");
return v + 2;
}).handle((v, e) -> {
log.debug("第三阶段");
if (e != null) {
log.debug("捕获上一阶段的异常:" + e.getMessage());
}
return v + 3;
}).whenComplete((v, e) -> {
log.debug("结果:" + v);
}).exceptionally(e -> {
log.debug("出现异常:" + e.getMessage());
return null;
});
pool.shutdown();
}
}
22:41:21.347 c.TestHandle [pool-1-thread-1] - 第一阶段 22:41:21.352 c.TestHandle [pool-1-thread-1] - 第三阶段 22:41:21.352 c.TestHandle [pool-1-thread-1] - 捕获上一阶段的异常:java.lang.ArithmeticException: / by zero 22:41:21.353 c.TestHandle [pool-1-thread-1] - 结果:null 22:41:21.353 c.TestHandle [pool-1-thread-1] - 出现异常:java.lang.NullPointerException
注意: 出现的空指针异常(v为null,出现在 return v + 2; 和 return v + 3;两处地方)
thenApply 与 thenCompose 对比- thenApply 和 thenCompose 都是将当前阶段的结果传递给下一个阶段,并返回一个新的 CompletionStage
- 类似于stream中的map和flatMap
public CompletionStage thenApply(Function super T, ? extends U> fn); public CompletionStage thenCompose(Function super T, ? extends CompletionStage> fn);
这两个方法的返回值都是CompletionStage,只是传入函数fn不同 对于thenApply,fn函数是对一个已完成的CompletionStage的返回值进行计算、操作 对于thenCompose,fn函数是对新创建的CompletionStage进行计算、操作
测试:
public static void main(String[] args) {
CompletableFuture thenApply = CompletableFuture.supplyAsync(() -> 1)
.thenApply(v -> "将Integer类型值:" + v + ",转换成字符串");
CompletableFuture thenCompose = CompletableFuture.supplyAsync(() -> 1)
.thenCompose(v -> CompletableFuture.supplyAsync(() -> "新创建一个CompletableFuture将Integer类型值:" + v + ",转换成字符串"));
System.out.println("thenApply:" +thenApply.join());
System.out.println("thenCompose:" + thenCompose.join());
}
thenApply:将Integer类型值:1,转换成字符串 thenCompose:新创建一个CompletableFuture将Integer类型值:1,转换成字符串whenComplete
- 返回一个与此阶段具有相同结果或异常的新 CompletionStage,它在此阶段完成时执行给定的操作。
- 当这个阶段完成时,使用这个阶段的结果(如果没有,则为 null)和异常(如果没有,则为 null)作为参数传递到下一个阶段。
- 当动作返回时,返回阶段完成。如果提供的操作本身遇到异常,则返回的阶段异常完成并出现此异常,除非此阶段也异常完成。
whenComplete 源码:
public CompletionStagewhenComplete(BiConsumer super T, ? super Throwable> action);
测试:
- 不传入线程池,使用默认线程池 —— ForkJoinPool.commonPool()
@Slf4j(topic = "c.TestWhenComplete")
public class TestWhenComplete {
public static void main(String[] args) throws InterruptedException {
CompletableFuture.supplyAsync(() -> {
// 随机数
int i = ThreadLocalRandom.current().nextInt(10);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("1s后返回结果:{}", i);
return i;
}).whenComplete((v, e) -> {
if (e == null) {
log.debug("没有异常,获取结果:{}", v);
}
}).exceptionally(e -> {
log.debug("出现异常:{}", e.getMessage());
return null;
});
log.debug("主线程去执行其他任务。。。");
log.debug("主线程执行完成");
}
}
18:18:51.912 c.TestWhenComplete [main] - 主线程去执行其他任务。。。 18:18:51.916 c.TestWhenComplete [main] - 主线程执行完成 Process finished with exit code 0
由结果可以看出,主线程停止后,子线程也随之停止 由于ForkJoinPool.commonPool()类似守护线程,还没执行完就随主线程一起关闭了 解决方法: 1. 让主线程执行完后,停留一段时间 2. 传入自定义线程池
- 让主线程执行完后,停留一段时间
@Slf4j(topic = "c.TestWhenComplete")
public class TestWhenComplete {
public static void main(String[] args) throws InterruptedException {
CompletableFuture.supplyAsync(() -> {
// 随机数
int i = ThreadLocalRandom.current().nextInt(10);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("1s后返回结果:{}", i);
return i;
}).whenComplete((v, e) -> {
if (e == null) {
log.debug("没有异常,获取结果:{}", v);
}
}).exceptionally(e -> {
log.debug("出现异常:{}", e.getMessage());
return null;
});
log.debug("主线程去执行其他任务。。。");
log.debug("主线程执行完成");
// 默认的线程池 —— ForkJoinPool.commonPool()类似守护线程,主线程关闭后,也会随之关闭
TimeUnit.SECONDS.sleep(2);
log.debug("主线程阻塞2s");
}
}
18:23:24.503 c.TestWhenComplete [main] - 主线程去执行其他任务。。。 18:23:24.506 c.TestWhenComplete [main] - 主线程执行完成 18:23:25.509 c.TestWhenComplete [ForkJoinPool.commonPool-worker-9] - 1s后返回结果:8 18:23:25.511 c.TestWhenComplete [ForkJoinPool.commonPool-worker-9] - 没有异常,获取结果:8 18:23:26.519 c.TestWhenComplete [main] - 主线程阻塞2s Process finished with exit code 0
- 传入自定义线程池
@Slf4j(topic = "c.TestWhenComplete")
public class TestWhenComplete {
public static void main(String[] args) throws InterruptedException {
ExecutorService pool = null;
try {
pool = Executors.newFixedThreadPool(3);
CompletableFuture.supplyAsync(() -> {
// 随机数
int i = ThreadLocalRandom.current().nextInt(10);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("1s后返回结果:{}", i);
return i;
// 传入自定义线程池
}, pool).whenComplete((v, e) -> {
if (e == null) {
log.debug("没有异常,获取结果:{}", v);
}
}).exceptionally(e -> {
log.debug("出现异常:{}", e.getMessage());
return null;
});
log.debug("主线程去执行其他任务。。。");
log.debug("主线程执行完成");
} catch (Exception e) {
e.printStackTrace();
} finally {
pool.shutdown();
}
}
}
18:37:39.968 c.TestWhenComplete [main] - 主线程去执行其他任务。。。 18:37:39.970 c.TestWhenComplete [main] - 主线程执行完成 18:37:40.973 c.TestWhenComplete [pool-1-thread-1] - 1s后返回结果:1 18:37:40.975 c.TestWhenComplete [pool-1-thread-1] - 没有异常,获取结果:1
- 模拟异常
@Slf4j(topic = "c.TestWhenComplete")
public class TestWhenComplete {
public static void main(String[] args) throws InterruptedException {
ExecutorService pool = null;
try {
pool = Executors.newFixedThreadPool(3);
CompletableFuture.supplyAsync(() -> {
// 随机数
int i = ThreadLocalRandom.current().nextInt(10);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("1s后返回结果:{}", i);
if (i > 2) {
// 模拟异常
i = 10/0;
}
return i;
// 传入自定义线程池
}, pool).whenComplete((v, e) -> {
if (e == null) {
log.debug("没有异常,获取结果:{}", v);
} else {
log.debug("捕获上一阶段异常:{}", e.getMessage());
}
}).exceptionally(e -> {
log.debug("出现异常:{}", e.getMessage());
return null;
});
log.debug("主线程去执行其他任务。。。");
log.debug("主线程执行完成");
} catch (Exception e) {
e.printStackTrace();
} finally {
pool.shutdown();
}
}
}
00:04:32.849 c.TestWhenComplete [main] - 主线程去执行其他任务。。。 00:04:32.851 c.TestWhenComplete [main] - 主线程执行完成 00:04:33.849 c.TestWhenComplete [pool-1-thread-1] - 1s后返回结果:4 00:04:33.854 c.TestWhenComplete [pool-1-thread-1] - 捕获上一阶段异常:java.lang.ArithmeticException: / by zero 00:04:33.854 c.TestWhenComplete [pool-1-thread-1] - 出现异常:java.lang.ArithmeticException: / by zeroexceptionally、whenComplete、handle 对比
public CompletionStageexceptionally(Function fn); public CompletionStage whenComplete(BiConsumer super T, ? super Throwable> action); public CompletionStage handle(BiFunction super T, Throwable, ? extends U> fn);
- exceptionally 处理上一个阶段的异常,并且可以再返回一个值
- handle 可以获取上一个阶段正常或异常的结果,并且有返回值,让下一个阶段继续执行
- whenComplete 可以获取上一个阶段正常或异常的结果,没有返回值
- 接收任务的处理结果,并消费处理,无返回结果
- 返回一个新的 CompletionStage,当此阶段正常完成时,将使用此阶段的结果作为提供的操作的参数来执行该新阶段
public CompletionStagethenAccept(Consumer super T> action);
测试:
@Slf4j(topic = "c.TestThenAccept")
public class TestThenAccept {
public static void main(String[] args) {
CompletableFuture thenAccept = CompletableFuture.supplyAsync(() -> {
log.debug("第一阶段");
return 1;
}).thenApply(v -> {
log.debug("第二阶段");
return v + 2;
}).thenApply(v -> {
log.debug("第三阶段");
return v + 3;
}).thenAccept(v -> {
log.debug("结果:" + v);
});
log.debug("返回结果:{}", thenAccept.join());
}
}
22:52:09.389 c.TestThenAccept [ForkJoinPool.commonPool-worker-1] - 第一阶段 22:52:09.394 c.TestThenAccept [ForkJoinPool.commonPool-worker-1] - 第二阶段 22:52:09.394 c.TestThenAccept [ForkJoinPool.commonPool-worker-1] - 第三阶段 22:52:09.394 c.TestThenAccept [ForkJoinPool.commonPool-worker-1] - 结果:6 22:52:09.394 c.TestThenAccept [main] - 返回结果:nullthenRun
- 如果任务A执行完后,再执行B时,B不需要A的结果
- 返回一个新的 CompletionStage,当此阶段正常完成时,执行给定的操作
public CompletionStagethenRun、thenAccept、thenApply 对比thenRun(Runnable action);
- thenRun:如果任务A执行完后,再执行B时,B不需要A的结果
- thenAccept:如果任务A执行完后,再执行B时,B需要A的结果,并且无返回值
- thenApply:如果任务A执行完后,再执行B时,B需要A的结果,并且有返回值
测试:
@Slf4j(topic = "c.TestThenRun_ThenAccept_ThenApply")
public class TestThenRun_ThenAccept_ThenApply {
public static void main(String[] args) {
CompletableFuture completableFuture1 = CompletableFuture.supplyAsync(() -> {
return 1;
}).thenRun(() -> {
log.debug("做其他任务");
});
log.debug("thenRun的结果:" + completableFuture1.join());
log.debug("===================");
CompletableFuture completableFuture2 = CompletableFuture.supplyAsync(() -> {
return 1;
}).thenAccept((v) -> {
log.debug("获取上一阶段的值:" + v);
});
log.debug("thenAccept的结果:" + completableFuture2.join());
log.debug("===================");
CompletableFuture completableFuture3 = CompletableFuture.supplyAsync(() -> {
return 1;
}).thenApply((v) -> {
log.debug("获取上一阶段的值:" + v);
return v + 2;
});
log.debug("thenApply的结果:" + completableFuture3.join());
}
}
22:58:08.055 c.TestThenRun_ThenAccept_ThenApply [main] - 做其他任务 22:58:08.059 c.TestThenRun_ThenAccept_ThenApply [main] - thenRun的结果:null 22:58:08.059 c.TestThenRun_ThenAccept_ThenApply [main] - =================== 22:58:08.060 c.TestThenRun_ThenAccept_ThenApply [main] - 获取上一阶段的值:1 22:58:08.061 c.TestThenRun_ThenAccept_ThenApply [main] - thenAccept的结果:null 22:58:08.061 c.TestThenRun_ThenAccept_ThenApply [main] - =================== 22:58:08.062 c.TestThenRun_ThenAccept_ThenApply [main] - 获取上一阶段的值:1 22:58:08.062 c.TestThenRun_ThenAccept_ThenApply [main] - thenApply的结果:3
注意: 由于各阶段任务做得很快,这时可能就会出现直接让main线程把任务做了,而没有开启新线程 如果在各阶段加一些休眠,可能就会开启新的线程做任务
给各阶段加休眠:
@Slf4j(topic = "c.TestThenRun_ThenAccept_ThenApply")
public class TestThenRun_ThenAccept_ThenApply {
public static void main(String[] args) {
CompletableFuture completableFuture1 = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
return 1;
}).thenRun(() -> {
log.debug("做其他任务");
});
log.debug("thenRun的结果:" + completableFuture1.join());
log.debug("===================");
CompletableFuture completableFuture2 = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
return 1;
}).thenAccept((v) -> {
log.debug("获取上一阶段的值:" + v);
});
log.debug("thenAccept的结果:" + completableFuture2.join());
log.debug("===================");
CompletableFuture completableFuture3 = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
return 1;
}).thenApply((v) -> {
log.debug("获取上一阶段的值:" + v);
return v + 2;
});
log.debug("thenApply的结果:" + completableFuture3.join());
}
}
23:03:51.729 c.TestThenRun_ThenAccept_ThenApply [ForkJoinPool.commonPool-worker-1] - 做其他任务 23:03:51.732 c.TestThenRun_ThenAccept_ThenApply [main] - thenRun的结果:null 23:03:51.732 c.TestThenRun_ThenAccept_ThenApply [main] - =================== 23:03:53.734 c.TestThenRun_ThenAccept_ThenApply [ForkJoinPool.commonPool-worker-1] - 获取上一阶段的值:1 23:03:53.734 c.TestThenRun_ThenAccept_ThenApply [main] - thenAccept的结果:null 23:03:53.734 c.TestThenRun_ThenAccept_ThenApply [main] - =================== 23:03:56.735 c.TestThenRun_ThenAccept_ThenApply [ForkJoinPool.commonPool-worker-1] - 获取上一阶段的值:1 23:03:56.735 c.TestThenRun_ThenAccept_ThenApply [main] - thenApply的结果:3thenRun 与 thenRunAsync 对比
- thenRun 使用的线程池会和上一阶段的线程池相同,并且线程相同(虽然是2个阶段,但对于CompletableFuture来说,是一个任务,只需开启一个线程工作)
- thenRunAsync 使用的线程池是默认的线程池 ForkJoinPool.commonPool(),并且CompletableFuture会把2个阶段的任务当做2个独立的任务(注意:这2个阶段的任务可能由一个线程完成,也可能由2个线程完成,取决于当时的cpu执行情况,所以在CompletableFuture 的很多场景中,可能会出现一个线程做多个阶段的任务)
thenAccept 与 thenAcceptAsync、thenApply 与 thenApplyAsync 同理
thenRunAsync 源码:
// CPU核数是否大于1
private static final boolean useCommonPool =(ForkJoinPool.getCommonPoolParallelism() > 1);
// CPU核数大于1,则使用默认线程池 ForkJoinPool.commonPool()
private static final Executor asyncPool = useCommonPool ?
ForkJoinPool.commonPool() : new ThreadPerTaskExecutor();
public CompletableFuture thenRunAsync(Runnable action) {
// CPU核数大于1, asyncPool 使用默认线程池 ForkJoinPool.commonPool()
return uniRunStage(asyncPool, action);
}
测试 thenRun:
@Slf4j(topic = "c.TestThenRun")
public class TestThenRun {
public static void main(String[] args) {
ExecutorService pool = Executors.newFixedThreadPool(2);
CompletableFuture completableFuture1 = CompletableFuture.supplyAsync(() -> {
log.debug("不传入自定义线程池时,supplyAsync 的线程池:" + Thread.currentThread().getName());
return 1;
}).thenRun(() -> {
log.debug("thenRun 和上一阶段使用相同的线程池:" + Thread.currentThread().getName());
});
log.debug("结果:" + completableFuture1.join());
log.debug("======================");
CompletableFuture completableFuture2 = CompletableFuture.supplyAsync(() -> {
log.debug("传入自定义线程池时,supplyAsync 的线程池:" + Thread.currentThread().getName());
return 1;
// 传入自定义线程池
}, pool).thenRun(() -> {
log.debug("thenRun 和上一阶段使用相同的线程池:" + Thread.currentThread().getName());
});
log.debug("结果:" + completableFuture2.join());
log.debug("======================");
// 关闭线程池
pool.shutdown();
}
}
23:08:39.413 c.TestThenRun [ForkJoinPool.commonPool-worker-1] - 不传入自定义线程池时,supplyAsync 的线程池:ForkJoinPool.commonPool-worker-1 23:08:39.424 c.TestThenRun [ForkJoinPool.commonPool-worker-1] - thenRun 和上一阶段使用相同的线程池:ForkJoinPool.commonPool-worker-1 23:08:39.425 c.TestThenRun [main] - 结果:null 23:08:39.425 c.TestThenRun [main] - ====================== 23:08:39.426 c.TestThenRun [pool-1-thread-1] - 传入自定义线程池时,supplyAsync 的线程池:pool-1-thread-1 23:08:39.426 c.TestThenRun [pool-1-thread-1] - thenRun 和上一阶段使用相同的线程池:pool-1-thread-1 23:08:39.426 c.TestThenRun [main] - 结果:null 23:08:39.426 c.TestThenRun [main] - ======================
测试 thenRunAsync:
@Slf4j(topic = "c.TestThenRunAsync")
public class TestThenRunAsync {
public static void main(String[] args) {
ExecutorService pool = Executors.newFixedThreadPool(2);
CompletableFuture completableFuture1 = CompletableFuture.supplyAsync(() -> {
log.debug("不传入自定义线程池时,supplyAsync 的线程池:" + Thread.currentThread().getName());
return 1;
}).thenRunAsync(() -> {
log.debug("thenRunAsync 使用默认的线程池:" + Thread.currentThread().getName());
});
log.debug("结果:" + completableFuture1.join());
log.debug("======================");
CompletableFuture completableFuture2 = CompletableFuture.supplyAsync(() -> {
log.debug("传入自定义线程池时,supplyAsync 的线程池:" + Thread.currentThread().getName());
return 1;
// 传入自定义线程池
}, pool).thenRunAsync(() -> {
log.debug("thenRunAsync 使用默认的线程池:" + Thread.currentThread().getName());
});
log.debug("结果:" + completableFuture2.join());
log.debug("======================");
// 关闭线程池
pool.shutdown();
}
}
23:10:21.374 c.TestThenRunAsync [ForkJoinPool.commonPool-worker-1] - 不传入自定义线程池时,supplyAsync 的线程池:ForkJoinPool.commonPool-worker-1 23:10:21.377 c.TestThenRunAsync [ForkJoinPool.commonPool-worker-1] - thenRunAsync 使用默认的线程池:ForkJoinPool.commonPool-worker-1 23:10:21.377 c.TestThenRunAsync [main] - 结果:null 23:10:21.377 c.TestThenRunAsync [main] - ====================== 23:10:21.378 c.TestThenRunAsync [pool-1-thread-1] - 传入自定义线程池时,supplyAsync 的线程池:pool-1-thread-1 23:10:21.378 c.TestThenRunAsync [ForkJoinPool.commonPool-worker-1] - thenRunAsync 使用默认的线程池:ForkJoinPool.commonPool-worker-1 23:10:21.378 c.TestThenRunAsync [main] - 结果:null 23:10:21.379 c.TestThenRunAsync [main] - ======================(4) 对计算速度进行选用类型的方法 applyToEither
- 返回最先完成任务的结果
- applyToEither 得到最先完成任务的结果,有返回值
- acceptEither 得到最先完成任务的结果,没有返回值
- runAfterEither 不关心最先完成任务的结果,没有返回值
public CompletionStage applyToEither(CompletionStage extends T> other, Function super T, U> fn); public CompletionStageacceptEither(CompletionStage extends T> other, Consumer super T> action); public CompletionStage runAfterEither(CompletionStage> other, Runnable action);
测试 applyToEither:
@Slf4j(topic = "c.TestApplyToEither")
public class TestApplyToEither {
public static void main(String[] args) {
CompletableFuture A = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("A 1s 完成任务");
return "A";
});
CompletableFuture B = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("B 2s 完成任务");
return "B";
});
CompletableFuture result = A.applyToEither(B, v -> v + " 先完成");
log.debug(result.join());
}
}
23:15:50.711 c.TestApplyToEither [ForkJoinPool.commonPool-worker-1] - A 1s 完成任务 23:15:50.714 c.TestApplyToEither [main] - A 先完成(5) 对计算结果进行合并类型的方法 thenCombine
- 合并2个阶段正常完成的结果
- 先完成的先等待
- thenCombine 合并前2个阶段正常完成的结果,有返回值
- thenAcceptBoth 合并前2个阶段正常完成的结果,没有返回值
- runAfterBoth 不关心前2个阶段正常完成的结果,没有返回值
public CompletionStagethenCombine(CompletionStage extends U> other, BiFunction super T,? super U,? extends V> fn); public CompletionStage thenAcceptBoth(CompletionStage extends U> other, BiConsumer super T, ? super U> action); public CompletionStage runAfterBoth(CompletionStage> other, Runnable action);
测试 thenCombine:
- 第一种写法
@Slf4j(topic = "c.TestThenCombine")
public class TestThenCombine {
public static void main(String[] args) {
log.debug("开始");
CompletableFuture A = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("A 完成");
return 10;
});
CompletableFuture B = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("B 完成");
return 20;
});
CompletableFuture result = A.thenCombine(B, (x, y) -> x + y);
log.debug("结果:" + result.join());
}
}
16:47:50.931 c.TestThenCombine [main] - 开始 16:47:51.992 c.TestThenCombine [ForkJoinPool.commonPool-worker-9] - A 完成 16:47:53.007 c.TestThenCombine [ForkJoinPool.commonPool-worker-2] - B 完成 16:47:53.007 c.TestThenCombine [main] - 结果:30
- 第二种写法
@Slf4j(topic = "c.TestThenCombine")
public class TestThenCombine {
public static void main(String[] args) {
log.debug("开始");
CompletableFuture result = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("A 完成");
return 10;
}).thenCombine(CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("B 完成");
return 20;
}), (x, y) -> x + y);
log.debug("结果:" + result.join());
}
}
16:53:50.817 c.TestThenCombine [main] - 开始 16:53:51.894 c.TestThenCombine [ForkJoinPool.commonPool-worker-9] - A 完成 16:53:52.886 c.TestThenCombine [ForkJoinPool.commonPool-worker-2] - B 完成 16:53:52.886 c.TestThenCombine [main] - 结果:305. CompletableFuture 性能 分析 CompletableFuture 完成异步任务
- 当 任务数 等于 CompletableFuture 线程池的最大线程数(CPU核心数 - 1) 时,CompletableFuture 性能最佳
例如:有4个核心,则 CompletableFuture 线程池的最大线程数是3,此时任务数为3(假设每个任务耗时1s),CompletableFuture 性能最 佳(CompletableFuture 做完3个任务耗时也就1s左右);
如果任务数为4-6,则 CompletableFuture 做完4-6个任务耗时就会是2s左右;
如果任务数为7-9,则 CompletableFuture 做完7-9个任务耗时就会是3s左右;
以此类推 - 所以当已知任务数量时,可以创建自定义线程池,手动设置线程池最大线程数
- 如果需要根据任务数量动态设置线程池最大线程数时,每执行完一批任务后,需要关闭线程池,等下一次新任务来时再根据任务数重新创建线程池
例如:第一次来了100个任务,则创建10个线程的线程池,执行完100个任务后关闭线程池;第二次来了200个任务时,再重新创建有20个线程的线程池;
测试 任务数 等于 CompletableFuture 线程池的最大线程数:
创建任务类:
@Slf4j(topic = "c.Task")
class Task {
// 任务名
private String name;
// 做该任务耗时 (秒)
private Integer productionTime;
public Task(String name, Integer productionTime) {
this.name = name;
this.productionTime = productionTime;
}
// 做任务
public void doTask() {
try {
TimeUnit.SECONDS.sleep(this.productionTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug(this.name + " 已做完");
}
}
第一种写法:
@Slf4j(topic = "c.TestCompletableFuture_doTask")
public class TestCompletableFuture_doTask {
public static void main(String[] args) {
log.debug("该二手台式机可用处理器数量:{}", Runtime.getRuntime().availableProcessors());
log.debug("当前线程池中的最大线程数:{}", ForkJoinPool.getCommonPoolParallelism());
log.debug("开始");
long startTime = System.currentTimeMillis();
// 创建任务
List tasks = new ArrayList<>();
for (int i = 1; i <= 3; i++) {
Task task = new Task("任务" + i, 1);
tasks.add(task);
}
// 做任务
List completableFutureList = new ArrayList<>();
for (Task task : tasks) {
CompletableFuture cf = CompletableFuture.runAsync(() -> task.doTask());
// 将所有任务加到 CompletableFuture 集合中
completableFutureList.add(cf);
}
// 等待 CompletableFuture 集合中的所有任务执行完毕
CompletableFuture.allOf(completableFutureList.toArray(new CompletableFuture[completableFutureList.size()])).join();
log.debug("做完所有任务的耗时:{}", (System.currentTimeMillis() - startTime));
}
}
00:50:41.392 c.TestCompletableFuture_doTask [main] - 该二手台式机可用处理器数量:4 00:50:41.392 c.TestCompletableFuture_doTask [main] - 当前线程池中的最大线程数:3 00:50:41.397 c.TestCompletableFuture_doTask [main] - 开始 00:50:42.467 c.Task [ForkJoinPool.commonPool-worker-3] - 任务3 已做完 00:50:42.467 c.Task [ForkJoinPool.commonPool-worker-2] - 任务2 已做完 00:50:42.467 c.Task [ForkJoinPool.commonPool-worker-1] - 任务1 已做完 00:50:42.467 c.TestCompletableFuture_doTask [main] - 做完所有任务的耗时:1069
第二种写法(用Stream简化代码,结果还是一样,主要是简化代码):
@Slf4j(topic = "c.TestCompletableFuture_doTask")
public class TestCompletableFuture_doTask {
public static void main(String[] args) {
log.debug("该二手台式机可用处理器数量:{}", Runtime.getRuntime().availableProcessors());
log.debug("当前线程池中的最大线程数:{}", ForkJoinPool.getCommonPoolParallelism());
log.debug("开始");
long startTime = System.currentTimeMillis();
// 将创建的任务加到 CompletableFuture 数组中 并执行
CompletableFuture[] tasks= IntStream.rangeClosed(1, 3)
.mapToObj(i -> new Task("任务" + i, 1))
.map(task -> CompletableFuture.runAsync(task::doTask))
.toArray(CompletableFuture[]::new);
// 等待 CompletableFuture 数组中的所有任务执行完
CompletableFuture.allOf(tasks).join();
log.debug("做完所有任务的耗时:{}", (System.currentTimeMillis() - startTime));
}
}
00:51:13.655 c.TestCompletableFuture_doTask [main] - 该二手台式机可用处理器数量:4 00:51:13.655 c.TestCompletableFuture_doTask [main] - 当前线程池中的最大线程数:3 00:51:13.667 c.TestCompletableFuture_doTask [main] - 开始 00:51:14.896 c.Task [ForkJoinPool.commonPool-worker-2] - 任务2 已做完 00:51:14.896 c.Task [ForkJoinPool.commonPool-worker-1] - 任务1 已做完 00:51:14.896 c.Task [ForkJoinPool.commonPool-worker-3] - 任务3 已做完 00:51:14.896 c.TestCompletableFuture_doTask [main] - 做完所有任务的耗时:1229
测试 任务数 超过 CompletableFuture 线程池的最大线程数:
@Slf4j(topic = "c.TestCompletableFuture_doTask")
public class TestCompletableFuture_doTask {
public static void main(String[] args) {
log.debug("该二手台式机可用处理器数量:{}", Runtime.getRuntime().availableProcessors());
log.debug("当前线程池中的最大线程数:{}", ForkJoinPool.getCommonPoolParallelism());
log.debug("开始");
long startTime = System.currentTimeMillis();
// 将创建的任务加到 CompletableFuture 数组中 并执行
CompletableFuture[] tasks= IntStream.rangeClosed(1, 3)
.mapToObj(i -> new Task("任务" + i, 1))
.map(task -> CompletableFuture.runAsync(task::doTask))
.toArray(CompletableFuture[]::new);
// 等待 CompletableFuture 数组中的所有任务执行完
CompletableFuture.allOf(tasks).join();
log.debug("做完所有任务的耗时:{}", (System.currentTimeMillis() - startTime));
}
}
01:06:31.516 c.TestCompletableFuture_doTask [main] - 该二手台式机可用处理器数量:4 01:06:31.516 c.TestCompletableFuture_doTask [main] - 当前线程池中的最大线程数:3 01:06:31.527 c.TestCompletableFuture_doTask [main] - 开始 01:06:32.636 c.Task [ForkJoinPool.commonPool-worker-2] - 任务2 已做完 01:06:32.636 c.Task [ForkJoinPool.commonPool-worker-1] - 任务1 已做完 01:06:32.637 c.Task [ForkJoinPool.commonPool-worker-3] - 任务3 已做完 01:06:33.637 c.Task [ForkJoinPool.commonPool-worker-2] - 任务4 已做完 01:06:33.637 c.TestCompletableFuture_doTask [main] - 做完所有任务的耗时:2110
CompletableFuture 配合 自定义线程池 使用
@Slf4j(topic = "c.TestCompletableFuture_doTask")
public class TestCompletableFuture_doTask {
public static void main(String[] args) {
ExecutorService pool = Executors.newCachedThreadPool();
log.debug("开始");
long startTime = System.currentTimeMillis();
// 将创建的任务加到 CompletableFuture 数组中 并执行
CompletableFuture[] tasks= IntStream.rangeClosed(1, 3)
.mapToObj(i -> new Task("任务" + i, 1))
.map(task -> CompletableFuture.runAsync(task::doTask, pool)) // 传入自定义线程池
.toArray(CompletableFuture[]::new);
// 等待 CompletableFuture 数组中的所有任务执行完
CompletableFuture.allOf(tasks).join();
log.debug("做完所有任务的耗时:{}", (System.currentTimeMillis() - startTime));
pool.shutdown();
}
}
01:32:25.794 c.TestCompletableFuture_doTask [main] - 开始 01:32:26.871 c.Task [pool-1-thread-10] - 任务10 已做完 01:32:26.872 c.Task [pool-1-thread-9] - 任务9 已做完 01:32:26.872 c.Task [pool-1-thread-8] - 任务8 已做完 01:32:26.872 c.Task [pool-1-thread-7] - 任务7 已做完 01:32:26.872 c.Task [pool-1-thread-6] - 任务6 已做完 01:32:26.873 c.Task [pool-1-thread-5] - 任务5 已做完 01:32:26.873 c.Task [pool-1-thread-4] - 任务4 已做完 01:32:26.873 c.Task [pool-1-thread-3] - 任务3 已做完 01:32:26.874 c.Task [pool-1-thread-2] - 任务2 已做完 01:32:26.874 c.Task [pool-1-thread-1] - 任务1 已做完 01:32:26.874 c.TestCompletableFuture_doTask [main] - 做完所有任务的耗时:1080
可以看到就算做10个任务,耗时也是1s左右,但不是任何数量的任务都只耗时1s 一台计算机,一瞬间处理的最大任务数量跟CPU的处理能力有关,也跟内存、硬盘、网卡等设备有关 具体跟什么有关,取决于任务本身所需要的资源 由于测试案例中的任务使用sleep模拟耗时的,但是sleep这个操作不太占CPU资源, 所以当传入自定义线程池后,任务数超过了CPU核心数后,耗时也不多四、CompletableFuture 使用背景 1. 引入CompletableFuture的背景
随着业务量的持续上升,各系统服务面临的压力也越来越大,业务对系统吞吐量的要求也越来越高。
例如:有一个对外暴露的接口服务,所有数据都需要通过该接口进行调度,最后再将数据进行聚合返回;
内部实现其实就是该服务对内调度各个下游服务,然后再获取数据进行聚合,这种方式具有鲜明的I/O密集型(I/O Bound)特点。
如果通过同步方式进行加载数据,则下游服务越多,耗时越长,因此考虑通过并行方式加载。
并行从下游获取数据,从IO模型上来讲分为同步模型和异步模型。
(1)同步模型从各个服务获取数据最常见的是同步调用,如下图所示:
在同步调用的场景下,接口耗时长、性能差,接口响应时长T > T1+T2+T3+……+Tn,这时为了缩短接口的响应时间,一般会使用线程池的方式并行获取数据,如下图所示:
这种方式由于以下两个原因,导致资源利用率比较低:
- CPU资源大量浪费在阻塞等待上,导致CPU资源利用率低。在Java 8之前,一般会通过回调的方式来减少阻塞,但是大量使用回调,又引发臭名昭著的回调地狱问题,导致代码可读性和可维护性大大降低。
- 为了增加并发度,会引入更多额外的线程池,随着CPU调度线程数的增加,会导致更严重的资源争用,宝贵的CPU资源被损耗在上下文切换上,而且线程本身也会占用系统资源,且不能无限增加。
同步模型下,会导致硬件资源无法充分利用,系统吞吐量容易达到瓶颈。
主要通过以下两种方式来减少线程池的调度开销和阻塞时间:
- 通过RPC NIO异步调用的方式可以降低线程数,从而降低调度(上下文切换)开销,如Dubbo的异步调用。
通过引入CompletableFuture(下文简称CF)对业务流程进行编排,降低依赖之间的阻塞。本文主要讲述CompletableFuture的使用和原理。
Future、CompletableFuture、RxJava、Reactor 对比如下:
- 可组合:可以将多个依赖操作通过不同的方式进行编排,例如CompletableFuture提供thenCompose、thenCombine等各种then开头的方法,这些方法就是对“可组合”特性的支持。
- 操作融合:将数据流中使用的多个操作符以某种方式结合起来,进而降低开销(时间、内存)。
- 延迟执行:操作不会立即执行,当收到明确指示时操作才会触发。例如:Reactor只有当有订阅者订阅时,才会触发操作。
- 回压:某些异步阶段的处理速度跟不上,直接失败会导致大量数据的丢失,对业务来说是不能接受的,这时需要反馈上游生产者降低调用量。
RxJava与Reactor显然更加强大,它们提供了更多的函数调用方式,支持更多特性,但同时也带来了更大的学习成本。而我们本次整合最需要的特性就是“异步”、“可组合”,综合考虑后,我们选择了学习成本相对较低的CompletableFuture。
4. CompletableFuture解决的问题CompletableFuture是由Java 8引入的,在Java8之前我们一般通过Future实现异步。
- Future用于表示异步计算的结果,只能通过阻塞或者轮询的方式获取结果,而且不支持设置回调方法,Java 8之前若要设置回调一般会使用guava的ListenableFuture,回调的引入又会导致臭名昭著的回调地狱。
- CompletableFuture对Future进行了扩展,可以通过设置回调的方式处理计算结果,同时也支持组合操作,支持进一步的编排,同时一定程度解决了回调地狱的问题。
下面将举例来说明,我们通过ListenableFuture、CompletableFuture来实现异步的差异。假设有三个操作step1、step2、step3存在依赖关系,其中step3的执行依赖step1和step2的结果。
Future(ListenableFuture)的实现(回调地狱)如下:
ExecutorService executor = Executors.newFixedThreadPool(5); ListeningExecutorService guavaExecutor = MoreExecutors.listeningDecorator(executor); ListenableFuturefuture1 = guavaExecutor.submit(() -> { //step 1 System.out.println("执行step 1"); return "step1 result"; }); ListenableFuture future2 = guavaExecutor.submit(() -> { //step 2 System.out.println("执行step 2"); return "step2 result"; }); ListenableFuture > future1And2 = Futures.allAsList(future1, future2); Futures.addCallback(future1And2, new FutureCallback
>() { @Override public void onSuccess(List
result) { System.out.println(result); ListenableFuture future3 = guavaExecutor.submit(() -> { System.out.println("执行step 3"); return "step3 result"; }); Futures.addCallback(future3, new FutureCallback () { @Override public void onSuccess(String result) { System.out.println(result); } @Override public void onFailure(Throwable t) { } }, guavaExecutor); } @Override public void onFailure(Throwable t) { }}, guavaExecutor);
CompletableFuture的实现如下:
ExecutorService executor = Executors.newFixedThreadPool(5); CompletableFuturecf1 = CompletableFuture.supplyAsync(() -> { System.out.println("执行step 1"); return "step1 result"; }, executor); CompletableFuture cf2 = CompletableFuture.supplyAsync(() -> { System.out.println("执行step 2"); return "step2 result"; }); cf1.thenCombine(cf2, (result1, result2) -> { System.out.println(result1 + " , " + result2); System.out.println("执行step 3"); return "step3 result"; }).thenAccept(result3 -> System.out.println(result3));
显然,CompletableFuture的实现更为简洁,可读性更好。
5. CompletableFuture的使用下面我们通过一个例子来讲解CompletableFuture如何使用,使用CompletableFuture也是构建依赖树的过程。一个CompletableFuture的完成会触发另外一系列依赖它的CompletableFuture的执行:
如上图所示,这里描绘的是一个业务接口的流程,其中包括CF1CF2CF3CF4CF5共5个步骤,并描绘了这些步骤之间的依赖关系,每个步骤可以是一次RPC调用、一次数据库操作或者是一次本地方法调用等,在使用CompletableFuture进行异步化编程时,图中的每个步骤都会产生一个CompletableFuture对象,最终结果也会用一个CompletableFuture来进行表示。
根据CompletableFuture依赖数量,可以分为以下几类:零依赖、一元依赖、二元依赖和多元依赖。
(1)零依赖:CompletableFuture的创建我们先看下如何不依赖其他CompletableFuture来创建新的CompletableFuture:
如上图红色链路所示,接口接收到请求后,首先发起两个异步调用CF1、CF2,主要有三种方式:
ExecutorService executor = Executors.newFixedThreadPool(5); //1、使用runAsync或supplyAsync发起异步调用 CompletableFuturecf1 = CompletableFuture.supplyAsync(() -> { return "result1"; }, executor); //2、CompletableFuture.completedFuture()直接创建一个已完成状态的CompletableFuture CompletableFuture cf2 = CompletableFuture.completedFuture("result2"); //3、先初始化一个未完成的CompletableFuture,然后通过complete()、completeExceptionally(),完成该CompletableFuture CompletableFuture cf = new CompletableFuture<>(); cf.complete("success");
第三种方式的一个典型使用场景,就是将回调方法转为CompletableFuture,然后再依赖CompletableFure的能力进行调用编排,示例如下:
@FunctionalInterface
public interface ThriftAsyncCall {
void invoke() throws TException;
}
public static CompletableFuture toCompletableFuture(final OctoThriftCallback,T> callback , ThriftAsyncCall thriftCall) {
//新建一个未完成的CompletableFuture
CompletableFuture resultFuture = new CompletableFuture<>();
//监听回调的完成,并且与CompletableFuture同步状态
callback.addObserver(new OctoObserver() {
@Override
public void onSuccess(T t) {
resultFuture.complete(t);
}
@Override
public void onFailure(Throwable throwable) {
resultFuture.completeExceptionally(throwable);
}
});
if (thriftCall != null) {
try {
thriftCall.invoke();
} catch (TException e) {
resultFuture.completeExceptionally(e);
}
}
return resultFuture;
}
(2)一元依赖:依赖一个CompletableFuture
如上图红色链路所示,CF3,CF5分别依赖于CF1和CF2,这种对于单个CompletableFuture的依赖可以通过thenApply、thenAccept、thenCompose等方法来实现,代码如下所示:
CompletableFuture(3)二元依赖:依赖两个CompletableFuturecf3 = cf1.thenApply(result1 -> { //result1为CF1的结果 //...... return "result3"; }); CompletableFuture cf5 = cf2.thenApply(result2 -> { //result2为CF2的结果 //...... return "result5"; });
如上图红色链路所示,CF4同时依赖于两个CF1和CF2,这种二元依赖可以通过thenCombine等回调来实现,如下代码所示:
CompletableFuture(4)多元依赖:依赖多个CompletableFuturecf4 = cf1.thenCombine(cf2, (result1, result2) -> { //result1和result2分别为cf1和cf2的结果 return "result4"; });
如上图红色链路所示,整个流程的结束依赖于三个步骤CF3、CF4、CF5,这种多元依赖可以通过allOf或anyOf方法来实现,区别是当需要多个依赖全部完成时使用allOf,当多个依赖中的任意一个完成即可时使用anyOf,以allOf为例,如下代码所示:
// 1.当需要多个依赖全部完成时使用allOf CompletableFuture五、CompletableFuture 原理 1. CompletableFuture 基本结构cf6 = CompletableFuture.allOf(cf3, cf4, cf5); CompletableFuture result = cf6.thenApply(v -> { //这里的join并不会阻塞,因为传给thenApply的函数是在CF3、CF4、CF5全部完成时,才会执行 。 result3 = cf3.join(); result4 = cf4.join(); result5 = cf5.join(); //根据result3、result4、result5组装最终result; return "result"; });
CompletableFuture中包含两个字段:result和stack。result用于存储当前CF的结果,stack(Completion)表示当前CF完成后需要触发的依赖动作(Dependency Actions),去触发依赖它的CF的计算,依赖动作可以有多个(表示有多个依赖它的CF),以栈(Treiber stack)的形式存储,stack表示栈顶元素。
这种方式类似“观察者模式”,依赖动作(Dependency Action)都封装在一个单独Completion子类中。下面是Completion类关系结构图。CompletableFuture中的每个方法都对应了图中的一个Completion的子类,Completion本身是观察者的基类。
- UniCompletion继承了Completion,是一元依赖的基类,例如thenApply的实现类UniApply就继承自UniCompletion。
- BiCompletion继承了UniCompletion,是二元依赖的基类,同时也是多元依赖的基类。例如thenCombine的实现类BiRelay就继承自BiCompletion。
按照类似“观察者模式”的设计思想,原理分析可以从“观察者”和“被观察者”两个方面着手。由于回调种类多,但结构差异不大,所以这里单以一元依赖中的thenApply为例,不再枚举全部回调类型。如下图所示:
被观察者:
- 每个CompletableFuture都可以被看作一个被观察者,其内部有一个Completion类型的链表成员变量stack,用来存储注册到其中的所有观察者。当被观察者执行完成后会弹栈stack属性,依次通知注册到其中的观察者。上面例子中步骤fn2就是作为观察者被封装在UniApply中。
- 被观察者CF中的result属性,用来存储返回结果数据。这里可能是一次RPC调用的返回值,也可能是任意对象,在上面的例子中对应步骤fn1的执行结果。
观察者:
CompletableFuture支持很多回调方法,例如thenAccept、thenApply、exceptionally等,这些方法接收一个函数类型的参数f,生成一个Completion类型的对象(即观察者),并将入参函数f赋值给Completion的成员变量fn,然后检查当前CF是否已处于完成状态(即result != null),如果已完成直接触发fn,否则将观察者Completion加入到CF的观察者链stack中,再次尝试触发,如果被观察者未执行完则其执行完毕之后通知触发。
- 观察者中的dep属性:指向其对应的CompletableFuture,在上面的例子中dep指向CF2。
- 观察者中的src属性:指向其依赖的CompletableFuture,在上面的例子中src指向CF1。
- 观察者Completion中的fn属性:用来存储具体的等待被回调的函数。这里需要注意的是不同的回调方法(thenAccept、thenApply、exceptionally等)接收的函数类型也不同,即fn的类型有很多种,在上面的例子中fn指向fn2。
这里仍然以thenApply为例来说明一元依赖的流程:
- 将观察者Completion注册到CF1,此时CF1将Completion压栈。
- 当CF1的操作运行完成时,会将结果赋值给CF1中的result属性。
- 依次弹栈,通知观察者尝试运行。
初步流程设计如上图所示,这里有几个关于注册与通知的并发问题,大家可以思考下:
Q1:在观察者注册之前,如果CF已经执行完成,并且已经发出通知,那么这时观察者由于错过了通知是不是将永远不会被触发呢 ? A1:不会。在注册时检查依赖的CF是否已经完成。如果未完成(即result == null)则将观察者入栈,如果已完成(result != null)则直接触发观察者操作。
Q2:在”入栈“前会有”result == null“的判断,这两个操作为非原子操作,CompletableFufure的实现也没有对两个操作进行加锁,完成时间在这两个操作之间,观察者仍然得不到通知,是不是仍然无法触发?
A2:不会。入栈之后再次检查CF是否完成,如果完成则触发。
Q3:当依赖多个CF时,观察者会被压入所有依赖的CF的栈中,每个CF完成的时候都会进行,那么会不会导致一个操作被多次执行呢 ?如下图所示,即当CF1、CF2同时完成时,如何避免CF3被多次触发。
A3:CompletableFuture的实现是这样解决该问题的:观察者在执行之前会先通过CAS操作设置一个状态位,将status由0改为1。如果观察者已经执行过了,那么CAS操作将会失败,取消执行。
通过对以上3个问题的分析可以看出,CompletableFuture在处理并行问题时,全程无加锁操作,极大地提高了程序的执行效率。我们将并行问题考虑纳入之后,可以得到完善的整体流程图如下所示:
CompletableFuture支持的回调方法十分丰富,但是正如上一章节的整体流程图所述,他们的整体流程是一致的。所有回调复用同一套流程架构,不同的回调监听通过策略模式实现差异化。
我们以thenCombine为例来说明二元依赖:
thenCombine操作表示依赖两个CompletableFuture。其观察者实现类为BiApply,如上图所示,BiApply通过src和snd两个属性关联被依赖的两个CF,fn属性的类型为BiFunction。与单个依赖不同的是,在依赖的CF未完成的情况下,thenCombine会尝试将BiApply压入这两个被依赖的CF的栈中,每个被依赖的CF完成时都会尝试触发观察者BiApply,BiApply会检查两个依赖是否都完成,如果完成则开始执行。这里为了解决重复触发的问题,同样用的是上一章节提到的CAS操作,执行时会先通过CAS设置状态位,避免重复触发。
依赖多个CompletableFuture的回调方法包括allOf、anyOf,区别在于allOf观察者实现类为BiRelay,需要所有被依赖的CF完成后才会执行回调;而anyOf观察者实现类为OrRelay,任意一个被依赖的CF完成后就会触发。二者的实现方式都是将多个被依赖的CF构建成一棵平衡二叉树,执行结果层层通知,直到根节点,触发回调监听。
要合理治理线程资源,最基本的前提条件就是要在写代码时,清楚地知道每一行代码都将执行在哪个线程上。下面我们看一下CompletableFuture的执行线程情况。
CompletableFuture实现了CompletionStage接口,通过丰富的回调方法,支持各种组合操作,每种组合场景都有同步和异步两种方法。
同步方法(即不带Async后缀的方法)有两种情况:
- 如果注册时被依赖的操作已经执行完成,则直接由当前线程执行。
- 如果注册时被依赖的操作还未执行完,则由回调线程执行。
异步方法(即带Async后缀的方法):可以选择是否传递线程池参数Executor运行在指定线程池中;当不传递Executor时,会使用ForkJoinPool中的共用线程池CommonPool(CommonPool的大小是CPU核数-1,如果是IO密集的应用,线程数可能成为瓶颈)。
例如:
ExecutorService threadPool1 = new ThreadPoolExecutor(10, 10, 0L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(100)); CompletableFuture2. 线程池须知 (1)异步回调要传线程池future1 = CompletableFuture.supplyAsync(() -> { System.out.println("supplyAsync 执行线程:" + Thread.currentThread().getName()); //业务操作 return ""; }, threadPool1); //此时,如果future1中的业务操作已经执行完毕并返回,则该thenApply直接由当前main线程执行;否则,将会由执行以上业务操作的threadPool1中的线程执行。 future1.thenApply(value -> { System.out.println("thenApply 执行线程:" + Thread.currentThread().getName()); return value + "1"; }); //使用ForkJoinPool中的共用线程池CommonPool future1.thenApplyAsync(value -> { //do something return value + "1"; }); //使用指定线程池 future1.thenApplyAsync(value -> { //do something return value + "1"; }, threadPool1);
前面提到,异步回调方法可以选择是否传递线程池参数Executor,这里我们建议强制传线程池,且根据实际情况做线程池隔离。
当不传递线程池时,会使用ForkJoinPool中的公共线程池CommonPool,这里所有调用将共用该线程池,核心线程数=处理器数量-1(单核核心线程数为1),所有异步回调都会共用该CommonPool,核心与非核心业务都竞争同一个池中的线程,很容易成为系统瓶颈。手动传递线程池参数可以更方便的调节参数,并且可以给不同的业务分配不同的线程池,以求资源隔离,减少不同业务之间的相互干扰。
(2) 线程池循环引用会导致死锁public Object doGet() {
ExecutorService threadPool1 = new ThreadPoolExecutor(10, 10, 0L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(100));
CompletableFuture cf1 = CompletableFuture.supplyAsync(() -> {
//do sth
return CompletableFuture.supplyAsync(() -> {
System.out.println("child");
return "child";
}, threadPool1).join();//子任务
}, threadPool1);
return cf1.join();
}
如上代码块所示,doGet方法第三行通过supplyAsync向threadPool1请求线程,并且内部子任务又向threadPool1请求线程。threadPool1大小为10,当同一时刻有10个请求到达,则threadPool1被打满,子任务请求线程时进入阻塞队列排队,但是父任务的完成又依赖于子任务,这时由于子任务得不到线程,父任务无法完成。主线程执行cf1.join()进入阻塞状态,并且永远无法恢复。
为了修复该问题,需要将父任务与子任务做线程池隔离,两个任务请求不同的线程池,避免循环依赖导致的阻塞。
(3) 异步RPC调用注意不要阻塞IO线程池服务异步化后很多步骤都会依赖于异步RPC调用的结果,这时需要特别注意一点,如果是使用基于NIO(比如Netty)的异步RPC,则返回结果是由IO线程负责设置的,即回调方法由IO线程触发,CompletableFuture同步回调(如thenApply、thenAccept等无Async后缀的方法)如果依赖的异步RPC调用的返回结果,那么这些同步回调将运行在IO线程上,而整个服务只有一个IO线程池,这时需要保证同步回调中不能有阻塞等耗时过长的逻辑,否则在这些逻辑执行完成前,IO线程将一直被占用,影响整个服务的响应。
3. 异常处理由于异步执行的任务在其他线程上执行,而异常信息存储在线程栈中,因此当前线程除非阻塞等待返回结果,否则无法通过trycatch捕获异常。CompletableFuture提供了异常捕获回调exceptionally,相当于同步调用中的trycatch。使用方法如下所示:
@Autowired private WmOrderAdditionInfoThriftService wmOrderAdditionInfoThriftService;//内部接口 public CompletableFuturegetCancelTypeAsync(long orderId) { CompletableFuture remarkResultFuture = wmOrderAdditionInfoThriftService.findOrderCancelledRemarkByOrderIdAsync(orderId);//业务方法,内部会发起异步rpc调用 return remarkResultFuture .exceptionally(err -> {//通过exceptionally 捕获异常,打印日志并返回默认值 log.error("WmOrderRemarkService.getCancelTypeAsync Exception orderId={}", orderId, err); return 0; }); }
有一点需要注意,CompletableFuture在回调方法中对异常进行了包装。大部分异常会封装成CompletionException后抛出,真正的异常存储在cause属性中,因此如果调用链中经过了回调方法处理那么就需要用Throwable.getCause()方法提取真正的异常。但是,有些情况下会直接返回真正的异常(Stack Overflow的讨论),最好使用工具类提取异常,如下代码所示:
@Autowired private WmOrderAdditionInfoThriftService wmOrderAdditionInfoThriftService;//内部接口 public CompletableFuturegetCancelTypeAsync(long orderId) { CompletableFuture remarkResultFuture = wmOrderAdditionInfoThriftService.findOrderCancelledRemarkByOrderIdAsync(orderId);//业务方法,内部会发起异步rpc调用 return remarkResultFuture .thenApply(result -> {//这里增加了一个回调方法thenApply,如果发生异常thenApply内部会通过new CompletionException(throwable) 对异常进行包装 //这里是一些业务操作 }) .exceptionally(err -> {//通过exceptionally 捕获异常,这里的err已经被thenApply包装过,因此需要通过Throwable.getCause()提取异常 log.error("WmOrderRemarkService.getCancelTypeAsync Exception orderId={}", orderId, ExceptionUtils.extractRealException(err)); return 0; }); }
上面代码中用到了一个自定义的工具类ExceptionUtils,用于CompletableFuture的异常提取,在使用CompletableFuture做异步编程时,可以直接使用该工具类处理异常。实现代码如下:
public class ExceptionUtils {
public static Throwable extractRealException(Throwable throwable) {
//这里判断异常类型是否为CompletionException、ExecutionException,如果是则进行提取,否则直接返回。
if (throwable instanceof CompletionException || throwable instanceof ExecutionException) {
if (throwable.getCause() != null) {
return throwable.getCause();
}
}
return throwable;
}
}
4. 自定义 CompletableFuture 工具方法
(1)自定义函数
@FunctionalInterface
public interface ThriftAsyncCall {
void invoke() throws TException ;
}
(2)CompletableFuture处理工具类
@Slf4j
public class FutureUtils {
public static CompletableFuture toCompletableFuture(final OctoThriftCallback,T> callback , ThriftAsyncCall thriftCall) {
CompletableFuture thriftResultFuture = new CompletableFuture<>();
callback.addObserver(new OctoObserver() {
@Override
public void onSuccess(T t) {
thriftResultFuture.complete(t);
}
@Override
public void onFailure(Throwable throwable) {
thriftResultFuture.completeExceptionally(throwable);
}
});
if (thriftCall != null) {
try {
thriftCall.invoke();
} catch (TException e) {
thriftResultFuture.completeExceptionally(e);
}
}
return thriftResultFuture;
}
public static CompletableFuture failed(Throwable ex) {
CompletableFuture completableFuture = new CompletableFuture<>();
completableFuture.completeExceptionally(ex);
return completableFuture;
}
public static CompletableFuture success(T result) {
CompletableFuture completableFuture = new CompletableFuture<>();
completableFuture.complete(result);
return completableFuture;
}
public static CompletableFuture> sequence(Collection> completableFutures) {
return CompletableFuture.allOf(completableFutures.toArray(new CompletableFuture>[0]))
.thenApply(v -> completableFutures.stream()
.map(CompletableFuture::join)
.collect(Collectors.toList())
);
}
public static CompletableFuture> sequenceList(Collection>> completableFutures) {
return CompletableFuture.allOf(completableFutures.toArray(new CompletableFuture>[0]))
.thenApply(v -> completableFutures.stream()
.flatMap( listFuture -> listFuture.join().stream())
.collect(Collectors.toList())
);
}
public static CompletableFuture
(3)异常提取工具类
public class ExceptionUtils {
public static Throwable extractRealException(Throwable throwable) {
if (throwable instanceof CompletionException || throwable instanceof ExecutionException) {
if (throwable.getCause() != null) {
return throwable.getCause();
}
}
return throwable;
}
}
(4)打印日志
@Slf4j public abstract class AbstractLogAction(5)日志处理实现类{ protected final String methodName; protected final Object[] args; public AbstractLogAction(String methodName, Object... args) { this.methodName = methodName; this.args = args; } protected void logResult(R result, Throwable throwable) { if (throwable != null) { boolean isBusinessError = throwable instanceof TBase || (throwable.getCause() != null && throwable .getCause() instanceof TBase); if (isBusinessError) { logBusinessError(throwable); } else if (throwable instanceof DegradeException || throwable instanceof DegradeRuntimeException) {//这里为内部rpc框架抛出的异常,使用时可以酌情修改 if (RhinoSwitch.getBoolean("isPrintDegradeLog", false)) { log.error("{} degrade exception, param:{} , error:{}", methodName, args, throwable); } } else { log.error("{} unknown error, param:{} , error:{}", methodName, args, ExceptionUtils.extractRealException(throwable)); } } else { if (isLogResult()) { log.info("{} param:{} , result:{}", methodName, args, result); } else { log.info("{} param:{}", methodName, args); } } } private void logBusinessError(Throwable throwable) { log.error("{} business error, param:{} , error:{}", methodName, args, throwable.toString(), ExceptionUtils.extractRealException(throwable)); } private boolean isLogResult() { //这里是动态配置开关,用于动态控制日志打印,开源动态配置中心可以使用nacos、apollo等,如果项目没有使用配置中心则可以删除 return RhinoSwitch.getBoolean(methodName + "_isLogResult", false); }}
@Slf4j public class LogErrorAction(6)打印日志方式extends AbstractLogAction implements BiConsumer { public LogErrorAction(String methodName, Object... args) { super(methodName, args); } @Override public void accept(R result, Throwable throwable) { logResult(result, throwable); } }
completableFuture
.whenComplete(
new LogErrorAction<>("orderService.getOrder", params));
(7)异常情况返回默认值
public class DefaultValueHandle(8)默认返回值应用示例extends AbstractLogAction implements BiFunction { private final R defaultValue; private final boolean isNullToDefault; public DefaultValueHandle(String methodName, R defaultValue, Object... args) { super(methodName, args); this.defaultValue = defaultValue; this.isNullToDefault = false; } public DefaultValueHandle(boolean isNullToDefault, R defaultValue, String methodName, Object... args) { super(methodName, args); this.defaultValue = defaultValue; this.isNullToDefault = isNullToDefault; } @Override public R apply(R result, Throwable throwable) { logResult(result, throwable); if (throwable != null) { return defaultValue; } if (result == null && isNullToDefault) { return defaultValue; } return result; } public static DefaultValueHandle.DefaultValueHandleBuilder builder() { return new DefaultValueHandle.DefaultValueHandleBuilder<>(); } public static class DefaultValueHandleBuilder { private boolean isNullToDefault; private R defaultValue; private String methodName; private Object[] args; DefaultValueHandleBuilder() { } public DefaultValueHandle.DefaultValueHandleBuilder isNullToDefault(final boolean isNullToDefault) { this.isNullToDefault = isNullToDefault; return this; } public DefaultValueHandle.DefaultValueHandleBuilder defaultValue(final R defaultValue) { this.defaultValue = defaultValue; return this; } public DefaultValueHandle.DefaultValueHandleBuilder methodName(final String methodName) { this.methodName = methodName; return this; } public DefaultValueHandle.DefaultValueHandleBuilder args(final Object... args) { this.args = args; return this; } public DefaultValueHandle build() { return new DefaultValueHandle (this.isNullToDefault, this.defaultValue, this.methodName, this.args); } public String toString() { return "DefaultValueHandle.DefaultValueHandleBuilder(isNullToDefault=" + this.isNullToDefault + ", defaultValue=" + this.defaultValue + ", methodName=" + this.methodName + ", args=" + Arrays.deepToString(this.args) + ")"; } }
completableFuture.handle(new DefaultValueHandle<>("orderService.getOrder", Collections.emptyMap(), params));



