文章目录
这一部分介绍CompletableFuture线程编排的使用。
- 195-商城业务-异步-CompletableFuture
- 196-商城业务-异步-CompletableFuture-启动异步任务
- 197-商城业务-异步-CompletableFuture-完成回调与异常感知
- 198-商城业务-异步-CompletableFuture-handle最终处理
- 199-商城业务-异步-CompletableFuture-线程串行化
- 200-商城业务-异步-CompletableFuture-两任务组合-都要完成
- 201-商城业务-异步-CompletableFuture-两任务组合-一个完成
- 202-商城业务-异步-CompletableFuture-多任务组合
一,CompletableFuture简介
商品详情页的业务场景涉及到多个步骤,每个步骤都需要一定的时间来完成。具体来说,这些步骤包括:
- 获取SKU的基本信息,耗时0.5秒。
- 获取SKU的图片信息,耗时0.5秒。
- 获取SKU的促销信息,耗时1秒。
- 获取SPU的所有销售属性,耗时1秒。
- 获取规格参数组及组下的规格参数,耗时1.5秒。
- 获取SPU详情,耗时1秒。
如果按照顺序执行这些步骤,总耗时将是6.5秒。
对于用户来说,等待这么长的时间来加载一个页面是不可接受的,尤其是在网络购物体验中,快速响应是提升用户体验的关键因素。
使用多线程的必要性:
-
提高响应速度:通过使用多线程,可以将这些步骤并行化,从而显著减少用户等待时间。如果这些步骤能够同时进行,理论上总耗时可以缩短至最长步骤的时间,即1.5秒。
-
资源利用优化:在某些步骤等待数据(如远程调用)时,其他线程可以继续执行其他任务,这样可以更有效地利用CPU资源。
-
改善用户体验:用户对页面加载速度非常敏感,多线程可以加快页面内容的呈现速度,提升用户体验。
-
并行处理:在多步骤的业务逻辑中,很多步骤可以独立进行,没有依赖关系。多线程允许这些步骤并行处理,而不是串行等待。
-
异步加载:现代Web应用常常采用异步加载的方式,即先展示页面的基础结构,然后逐步加载其他数据。多线程可以支持这种模式,先快速展示基本信息,再逐步补充其他内容。
CompletableFuture
CompletableFuture
是 Java 8 引入的一个非常重要的类,它提供了一种异步编程的解决方案,用于编写非阻塞的、基于回调的代码。CompletableFuture
允许你以声明性的方式处理异步操作,使得代码更加简洁和易于管理。
以下是 CompletableFuture
的一些关键特性:
-
异步执行:
CompletableFuture
可以异步执行任务,而不会阻塞当前线程。 -
链式调用:通过方法引用和 lambda 表达式,
CompletableFuture
支持链式调用,允许你将多个异步操作链接在一起。 -
回调:你可以为
CompletableFuture
添加回调方法,当操作完成或发生异常时,这些回调将被执行。 -
组合操作:
CompletableFuture
允许你组合多个异步操作,例如thenCombine
、thenAcceptBoth
等。 -
异常处理:
CompletableFuture
提供了exceptionally
方法,用于处理异步操作中发生的异常。 -
转换结果:
CompletableFuture
的thenApply
方法可以用来转换异步操作的结果。 -
等待完成:
CompletableFuture
的get
方法可以等待异步操作完成,并返回结果。 -
取消操作:
CompletableFuture
支持取消尚未完成的异步操作。
以下是一个简单的 CompletableFuture
示例,演示了如何使用它来异步执行任务并处理结果:
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class CompletableFutureExample {
public static void main(String[] args) {
// 创建一个CompletableFuture任务
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(2000); // 模拟耗时操作
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return "Hello, CompletableFuture!";
});
// 添加回调来处理结果
future.thenAccept(result -> {
System.out.println("Received result: " + result);
});
// 转换结果
CompletableFuture<Integer> transformed = future.thenApply(s -> s.length());
// 等待转换结果并处理
try {
int resultLength = transformed.get();
System.out.println("Result length: " + resultLength);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
在这个示例中,创建了一个异步任务来返回一个字符串,然后通过 thenAccept
方法添加了一个回调来处理这个结果。
接着使用 thenApply
方法将结果转换为字符串的长度,并等待这个转换结果。CompletableFuture
使得整个异步处理过程变得简单而清晰。
二,CompletableFuture-启动异步任务的四个方法
CompletableFuture类提供的四个静态方法,它们都是异步执行任务的方法。
-
static CompletableFuture<Void> runAsync(Runnable runnable)
这个方法接受一个Runnable对象作为参数,用于异步执行一个无返回值的任务。该方法内部会使用ForkJoinPool.commonPool()作为默认的线程池来执行任务。例如:CompletableFuture.runAsync(() -> { System.out.println("Task executed asynchronously"); });
上面的代码会异步执行打印语句。
-
public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor)
这个方法与上一个方法类似,但是允许用户自定义线程池来执行任务。例如:ExecutorService executor = Executors.newFixedThreadPool(5); CompletableFuture.runAsync(() -> { System.out.println("Task executed asynchronously using custom executor"); }, executor);
上面的代码会使用一个包含5个线程的固定线程池来异步执行打印语句。
-
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
这个方法接受一个Supplier对象作为参数,用于异步执行一个有返回值的任务。同样,该方法也会使用ForkJoinPool.commonPool()作为默认的线程池来执行任务。例如:CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> { return computeExpensiveResult(); });
上面的代码会异步计算一个昂贵的结果,并返回一个CompletableFuture对象,可以通过future.get()方法获取结果。
-
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)
这个方法与上一个方法类似,但允许用户自定义线程池来执行任务。例如:ExecutorService executor = Executors.newFixedThreadPool(5); CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> { return computeExpensiveResult(); }, executor);
上面的代码会使用一个包含5个线程的固定线程池来异步计算一个昂贵的结果,并返回一个CompletableFuture对象。
三,编排1:获取异步任务结果和处理异常
- whenComplete 获取结果,可以感知异常
- exceptionally处理异常,并设置默认值
package com.atguigu.gulimall.search.threads;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadTest {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(10);
CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
int i = 100/0;
return 10;
}, executorService);
// 获取结果,根据是否有隐藏做不桶处理,不能设置默认值
CompletableFuture<Integer> completableFuture1 = completableFuture.whenComplete((res, exc) -> {
if (exc != null) {
System.out.println("有异常+" + exc);
}
System.out.println("异步任务的结果是" + res);
});
// 有异常会回调,可以设置默认值
CompletableFuture<Integer> completableFuture2 = completableFuture1.exceptionally((ex) -> {
System.out.println("有异常+" + ex);
return 100;
});
completableFuture2.whenComplete((res, exc)->{
System.out.println("res="+res);
});
}
}
四,编排2:多线程串行
图中展示的是Java 8中CompletableFuture类的一些常用方法,主要用于链式处理异步任务。这些方法分为三组:
- thenApply系列方法,有返回值
- thenRun系列方法,无返回值,不能拿到上一步的结果
- thenAccept系列方法,无返回值,可以拿到上一步的结果
1. thenApply系列方法:
-
thenApply(Function<? super T, ? extends U> fn)
:这个方法接收一个Function对象作为参数,用于转换当前CompletableFuture的结果。当当前CompletableFuture完成后,会应用传入的函数并将结果传递给下一个CompletableFuture。例如:CompletableFuture<String> future1 = CompletableFuture.completedFuture("Hello"); CompletableFuture<Integer> future2 = future1.thenApply(String::length);
在上面的例子中,future1是一个已完成的CompletableFuture,它的结果是字符串"Hello"。future2是future1的一个后续操作,它会计算字符串的长度。
-
thenApplyAsync(Function<? super T, ? extends U> fn)
:这个方法与thenApply类似,不同之处在于它是异步执行的。也就是说,转换操作不会阻塞当前线程,而是由另一个线程来执行。例如:CompletableFuture<String> future1 = CompletableFuture.completedFuture("Hello"); CompletableFuture<Integer> future2 = future1.thenApplyAsync(String::length);
在上面的例子中,future2的转换操作是由另一个线程异步执行的。
-
thenApplyAsync(Function<? super T, ? extends U> fn, Executor executor)
:这个方法与thenApplyAsync类似,但它允许用户自定义线程池来执行转换操作。例如:ExecutorService executor = Executors.newFixedThreadPool(5); CompletableFuture<String> future1 = CompletableFuture.completedFuture("Hello"); CompletableFuture<Integer> future2 = future1.thenApplyAsync(String::length, executor);
在上面的例子中,future2的转换操作是由一个包含5个线程的固定线程池来异步执行的。
2. thenAccept系列方法:
-
thenAccept(Consumer<? super T> action)
:这个方法接收一个Consumer对象作为参数,用于消费当前CompletableFuture的结果。当当前CompletableFuture完成后,会应用传入的操作,但不返回任何结果。例如:CompletableFuture<String> future1 = CompletableFuture.completedFuture("Hello"); future1.thenAccept(System.out::println);
在上面的例子中,future1的结果会被打印出来。
-
thenAcceptAsync(Consumer<? super T> action)
:这个方法与thenAccept类似,不同之处在于它是异步执行的。例如:CompletableFuture<String> future1 = CompletableFuture.completedFuture("Hello"); future1.thenAcceptAsync(System.out::println);
在上面的例子中,打印操作是由另一个线程异步执行的。
-
thenAcceptAsync(Consumer<? super T> action, Executor executor)
:这个方法与thenAcceptAsync类似,但它允许用户自定义线程池来执行消费操作。例如:ExecutorService executor = Executors.newFixedThreadPool(5); CompletableFuture<String> future1 = CompletableFuture.completedFuture("Hello"); future1.thenAcceptAsync(System.out::println, executor);
在上面的例子中,打印操作是由一个包含5个线程的固定线程池来异步执行的。
3. thenRun系列方法:
-
thenRun(Runnable action)
:这个方法接收一个Runnable对象作为参数,在当前CompletableFuture完成后执行。它不依赖于当前CompletableFuture的结果,只是简单地执行后续操作。例如:CompletableFuture<String> future1 = CompletableFuture.completedFuture("Hello"); future1.thenRun(() -> System.out.println("Task completed"));
在上面的例子中,无论future1的结果是什么,都会输出"Task completed"。
-
thenRunAsync(Runnable action)
:这个方法与thenRun类似,不同之处在于它是异步执行的。例如:CompletableFuture<String> future1 = CompletableFuture.completedFuture("Hello"); future1.thenRunAsync(() -> System.out.println("Task completed"));
在上面的例子中,后续操作是由另一个线程异步执行的。
-
thenRunAsync(Runnable action, Executor executor)
:这个方法与thenRunAsync类似,但它允许用户自定义线程池来执行后续操作。例如:ExecutorService executor = Executors.newFixedThreadPool(5); CompletableFuture<String> future1 = CompletableFuture.completedFuture("Hello"); future1.thenRunAsync(() -> System.out.println("Task completed"), executor);
在上面的例子中,后续操作是由一个包含5个线程的固定线程池来异步执行的。
五,编排3:两个异步任务组合为一个任务
把两个任务组合为一个任务,只有两个任务都完成了,才会执行后续任务。
1. thenCombine系列方法
-
thenCombine(CompletionStage<? extends U> other, BiFunction<? super T,? super U,? extends V> fn)
:这个方法用于组合两个CompletableFuture,当这两个CompletableFuture都完成后,会应用传入的BiFunction对象并将结果传递给一个新的CompletableFuture。例如:CompletableFuture<String> future1 = CompletableFuture.completedFuture("Hello"); CompletableFuture<Integer> future2 = CompletableFuture.completedFuture(5); CompletableFuture<String> combined = future1.thenCombine(future2, (s, i) -> s + " " + i);
在上面的例子中,combined是一个新的CompletableFuture,它会等待future1和future2都完成后,将它们的结果合并成一个字符串。
-
thenCombineAsync(CompletionStage<? extends U> other, BiFunction<? super T,? super U,? extends V> fn)
:这个方法与thenCombine类似,不同之处在于它是异步执行的。例如:CompletableFuture<String> future1 = CompletableFuture.completedFuture("Hello"); CompletableFuture<Integer> future2 = CompletableFuture.completedFuture(5); CompletableFuture<String> combined = future1.thenCombineAsync(future2, (s, i) -> s + " " + i);
在上面的例子中,合并操作是由另一个线程异步执行的。
-
thenCombineAsync(CompletionStage<? extends U> other, BiFunction<? super T,? super U,? extends V> fn, Executor executor)
:这个方法与thenCombineAsync类似,但它允许用户自定义线程池来执行合并操作。例如:ExecutorService executor = Executors.newFixedThreadPool(5); CompletableFuture<String> future1 = CompletableFuture.completedFuture("Hello"); CompletableFuture<Integer> future2 = CompletableFuture.completedFuture(5); CompletableFuture<String> combined = future1.thenCombineAsync(future2, (s, i) -> s + " " + i, executor);
在上面的例子中,合并操作是由一个包含5个线程的固定线程池来异步执行的。
2. thenAcceptBoth系列方法
-
thenAcceptBoth(CompletionStage<? extends U> other, BiConsumer<? super T,? super U> action)
:这个方法用于组合两个CompletableFuture,当这两个CompletableFuture都完成后,会应用传入的BiConsumer对象来消费它们的结果。例如:CompletableFuture<String> future1 = CompletableFuture.completedFuture("Hello"); CompletableFuture<Integer> future2 = CompletableFuture.completedFuture(5); future1.thenAcceptBoth(future2, (s, i) -> System.out.println(s + " " + i));
在上面的例子中,当future1和future2都完成后,会打印出它们的结果。
-
thenAcceptBothAsync(CompletionStage<? extends U> other, BiConsumer<? super T,? super U> action)
:这个方法与thenAcceptBoth类似,不同之处在于它是异步执行的。例如:CompletableFuture<String> future1 = CompletableFuture.completedFuture("Hello"); CompletableFuture<Integer> future2 = CompletableFuture.completedFuture(5); future1.thenAcceptBothAsync(future2, (s, i) -> System.out.println(s + " " + i));
在上面的例子中,消费操作是由另一个线程异步执行的。
-
thenAcceptBothAsync(CompletionStage<? extends U> other, BiConsumer<? super T,? super U> action, Executor executor)
:这个方法与thenAcceptBothAsync类似,但它允许用户自定义线程池来执行消费操作。例如:ExecutorService executor = Executors.newFixedThreadPool(5); CompletableFuture<String> future1 = CompletableFuture.completedFuture("Hello"); CompletableFuture<Integer> future2 = CompletableFuture.completedFuture(5); future1.thenAcceptBothAsync(future2, (s, i) -> System.out.println(s + " " + i), executor);
在上面的例子中,消费操作是由一个包含5个线程的固定线程池来异步执行的。
3. runAfterBoth系列方法
-
runAfterBoth(CompletionStage<?> other, Runnable action)
:这个方法用于组合两个CompletableFuture,当这两个CompletableFuture都完成后,会执行传入的Runnable对象。例如:CompletableFuture<String> future1 = CompletableFuture.completedFuture("Hello"); CompletableFuture<Integer> future2 = CompletableFuture.completedFuture(5); future1.runAfterBoth(future2, () -> System.out.println("Both tasks completed"));
在上面的例子中,当future1和future2都完成后,会输出"Both tasks completed"。
-
runAfterBothAsync(CompletionStage<?> other, Runnable action)
:这个方法与runAfterBoth类似,不同之处在于它是异步执行的。例如:CompletableFuture<String> future1 = CompletableFuture.completedFuture("Hello"); CompletableFuture<Integer> future2 = CompletableFuture.completedFuture(5); future1.runAfterBothAsync(future2, () -> System.out.println("Both tasks completed"));
在上面的例子中,后续操作是由另一个线程异步执行的。
-
runAfterBothAsync(CompletionStage<?> other, Runnable action, Executor executor)
:这个方法与runAfterBothAsync类似,但它允许用户自定义线程池来执行后续操作。例如:ExecutorService executor = Executors.newFixedThreadPool(5); CompletableFuture<String> future1 = CompletableFuture.completedFuture("Hello"); CompletableFuture<Integer> future2 = CompletableFuture.completedFuture(5); future1.runAfterBothAsync(future2, () -> System.out.println("Both tasks completed"), executor);
在上面的例子中,后续操作是由一个包含5个线程的固定线程池来异步执行的。
五,编排4:2个完成一个即可
CompletableFuture类的另外三个方法,它们用于处理两个CompletableFuture中的任意一个完成的情况。这些方法可以帮助我们在两个异步任务中选择一个先完成的任务进行后续处理。下面逐一解释它们的使用和区别:
1,applyToEither系列方法
-
applyToEither(CompletionStage<? extends U> other, Function<? super U, V> fn)
:这个方法用于在两个CompletableFuture中选择第一个完成的任务,然后应用传入的Function对象并返回一个新的CompletableFuture。例如:CompletableFuture<String> future1 = CompletableFuture.completedFuture("Hello"); CompletableFuture<Integer> future2 = CompletableFuture.completedFuture(5).thenDelay(1000, TimeUnit.MILLISECONDS); CompletableFuture<String> either = future1.applyToEither(future2, String::valueOf);
在上面的例子中,either是一个新的CompletableFuture,它会选择首先完成的任务(在这个例子中是future1),并将其结果转换为字符串。
-
applyToEitherAsync(CompletionStage<? extends U> other, Function<? super U, V> fn)
:这个方法与applyToEither类似,不同之处在于它是异步执行的。例如:CompletableFuture<String> future1 = CompletableFuture.completedFuture("Hello"); CompletableFuture<Integer> future2 = CompletableFuture.completedFuture(5).thenDelay(1000, TimeUnit.MILLISECONDS); CompletableFuture<String> either = future1.applyToEitherAsync(future2, String::valueOf);
在上面的例子中,转换操作是由另一个线程异步执行的。
-
applyToEitherAsync(CompletionStage<? extends U> other, Function<? super U, V> fn, Executor executor)
:这个方法与applyToEitherAsync类似,但它允许用户自定义线程池来执行转换操作。例如:ExecutorService executor = Executors.newFixedThreadPool(5); CompletableFuture<String> future1 = CompletableFuture.completedFuture("Hello"); CompletableFuture<Integer> future2 = CompletableFuture.completedFuture(5).thenDelay(1000, TimeUnit.MILLISECONDS); CompletableFuture<String> either = future1.applyToEitherAsync(future2, String::valueOf, executor);
在上面的例子中,转换操作是由一个包含5个线程的固定线程池来异步执行的。
2. acceptEither系列方法
-
acceptEither(CompletionStage<? extends U> other, Consumer<? super U> action)
:这个方法用于在两个CompletableFuture中选择第一个完成的任务,然后应用传入的Consumer对象来消费其结果。例如:CompletableFuture<String> future1 = CompletableFuture.completedFuture("Hello"); CompletableFuture<Integer> future2 = CompletableFuture.completedFuture(5).thenDelay(1000, TimeUnit.MILLISECONDS); future1.acceptEither(future2, System.out::println);
在上面的例子中,当其中一个任务完成时,会打印出其结果。
-
acceptEitherAsync(CompletionStage<? extends U> other, Consumer<? super U> action)
:这个方法与acceptEither类似,不同之处在于它是异步执行的。例如:CompletableFuture<String> future1 = CompletableFuture.completedFuture("Hello"); CompletableFuture<Integer> future2 = CompletableFuture.completedFuture(5).thenDelay(1000, TimeUnit.MILLISECONDS); future1.acceptEitherAsync(future2, System.out::println);
在上面的例子中,消费操作是由另一个线程异步执行的。
-
acceptEitherAsync(CompletionStage<? extends U> other, Consumer<? super U> action, Executor executor)
:这个方法与acceptEitherAsync类似,但它允许用户自定义线程池来执行消费操作。例如:ExecutorService executor = Executors.newFixedThreadPool(5); CompletableFuture<String> future1 = CompletableFuture.completedFuture("Hello"); CompletableFuture<Integer> future2 = CompletableFuture.completedFuture(5).thenDelay(1000, TimeUnit.MILLISECONDS); future1.acceptEitherAsync(future2, System.out::println, executor);
在上面的例子中,消费操作是由一个包含5个线程的固定线程池来异步执行的。
3. runAfterEither系列方法
-
runAfterEither(CompletionStage<?> other, Runnable action)
:这个方法用于在两个CompletableFuture中选择第一个完成的任务,然后执行传入的Runnable对象。例如:CompletableFuture<String> future1 = CompletableFuture.completedFuture("Hello"); CompletableFuture<Integer> future2 = CompletableFuture.completedFuture(5).thenDelay(1000, TimeUnit.MILLISECONDS); future1.runAfterEither(future2, () -> System.out.println("One of the tasks completed"));
在上面的例子中,当其中一个任务完成时,会输出"One of the tasks completed"。
-
runAfterEitherAsync(CompletionStage<?> other, Runnable action)
:这个方法与runAfterEither类似,不同之处在于它是异步执行的。例如:CompletableFuture<String> future1 = CompletableFuture.completedFuture("Hello"); CompletableFuture<Integer> future2 = CompletableFuture.completedFuture(5).thenDelay(1000, TimeUnit.MILLISECONDS); future1.runAfterEitherAsync(future2, () -> System.out.println("One of the tasks completed"));
在上面的例子中,后续操作是由另一个线程异步执行的。
-
runAfterEitherAsync(CompletionStage<?> other, Runnable action, Executor executor)
:这个方法与runAfterEitherAsync类似,但它允许用户自定义线程池来执行后续操作。例如:ExecutorService executor = Executors.newFixedThreadPool(5); CompletableFuture<String> future1 = CompletableFuture.completedFuture("Hello"); CompletableFuture<Integer> future2 = CompletableFuture.completedFuture(5).thenDelay(1000, TimeUnit.MILLISECONDS); future1.runAfterEitherAsync(future2, () -> System.out.println("One of the tasks completed"), executor);
在上面的例子中,后续操作是由一个包含5个线程的固定线程池来异步执行的。
六,编排5:编排多个任务
1. allOf()方法:多个任务全部完成才行
public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs)
:这个方法用于创建一个新的CompletableFuture,表示所有输入的CompletableFuture都已完成。例如:
在上面的例子中,all是一个新的CompletableFuture,它会在future1和future2都完成后才完成。CompletableFuture<String> future1 = CompletableFuture.completedFuture("Hello"); CompletableFuture<Integer> future2 = CompletableFuture.completedFuture(5); CompletableFuture<Void> all = CompletableFuture.allOf(future1, future2); all.get();
当所有的CompletableFuture都完成后,可以通过all.join()
或者all.get()
获取结果。由于all本身没有结果,所以它的类型是Void。如果任何一个CompletableFuture失败了,那么all也会立即失败,并抛出相应的异常。
2. anyOf()方法:一个任务完成即可
public static CompletableFuture<Object> anyOf(CompletableFuture<?>... cfs)
:这个方法用于创建一个新的CompletableFuture,表示至少有一个输入的CompletableFuture已经完成。例如:
在上面的例子中,any是一个新的CompletableFuture,它会在future1或future2中任一完成时就完成。CompletableFuture<String> future1 = CompletableFuture.completedFuture("Hello"); CompletableFuture<Integer> future2 = CompletableFuture.completedFuture(5); CompletableFuture<Object> any = CompletableFuture.anyOf(future1, future2);
当至少有一个CompletableFuture完成时,可以通过any.join()
或者any.get()
获取结果。由于any可能由任何CompletableFuture的结果决定,所以它的类型是Object。如果所有的CompletableFuture都失败了,那么any也会失败,并抛出最后一个发生的异常。