Future相关并发类使用
- Future相关并发类使用
- 一、Callable&Future&FutureTask 详解
- 1. 基础组件对比:Runnable vs Callable
- 2. Future 接口:任务管理工具
- 3. FutureTask:Runnable 与 Future 的结合体
- 4. Future 的局限性
- 二、CompletableFuture:Future 的扩展与增强
- 1. 核心定位
- 2. 异步任务创建:4 个静态方法
- 3. 结果获取:join () vs get ()
- 4. 核心功能:任务编排与结果处理
- 5. 实战案例:烧水泡茶(对比 Future)
- 关键问题
- 问题 1:Future 相比 Runnable 有哪些核心优势?又存在哪些无法避免的局限性?
- 问题 2:CompletableFuture 如何实现 “任务依赖关系” 的编排?请结合具体方法和案例说明。
- 问题 3:在 “促销活动商品信息查询” 场景中,同步查询存在什么问题?使用 Future 改造的核心优势是什么?具体如何实现?
- 一、Callable&Future&FutureTask 详解
一、Callable&Future&FutureTask 详解
1. 基础组件对比:Runnable vs Callable
对比维度 | Runnable 接口 | Callable |
---|---|---|
返回值 | 无(run () 返回 void) | 有(call () 返回 V 类型) |
异常处理 | 不能抛出 checked Exception | 可抛出 checked Exception(声明在 call () 方法) |
函数式接口方法 | void run() | V call() throws Exception |
配合组件 | Thread(直接作为线程任务) | Future/FutureTask(获结果) |
2. Future 接口:任务管理工具
- 核心 API 说明:
boolean cancel(boolean mayInterruptIfRunning)
:取消任务,参数指定是否立即中断运行中任务boolean isCancelled()
:判断任务是否在正常完成前被取消(是则返回 true)boolean isDone()
:判断任务是否完成(正常终止、异常、取消均返回 true)V get()
:阻塞等待任务完成,获 V 类型结果,抛 InterruptedException(线程被中断)、ExecutionException(任务执行异常)、CancellationException(任务被取消)V get(long timeout, TimeUnit unit)
:同上,增加超时时间,超时抛 TimeoutException
- 本质:对 Runnable/Callable 任务的执行结果进行管理,但存在明显局限性。
3. FutureTask:Runnable 与 Future 的结合体
- 特性:实现
RunnableFuture
接口(继承 Runnable 和 Future),既可以作为线程任务(Runnable)执行,也可以作为 Future 获取 Callable 的返回结果。 - 使用流程:
- 构建 Callable 实例;
- 将 Callable 实例传入 FutureTask 构造函数,创建 FutureTask 对象;
- 将 FutureTask 作为 Runnable 传入 Thread 或线程池执行;
- 通过 FutureTask 的 get () 方法获取任务结果。
- 实战案例:促销商品信息查询
- 同步查询问题:商品基本信息、价格、库存等 5 个接口各需 50ms,同步执行总耗时 200-300ms;
- Future 改造方案:创建 5 个 FutureTask(分别对应 5 个查询任务),提交到线程池并行执行,总耗时约等于最长任务耗时(50ms);
- 核心代码:通过
ExecutorService
提交 5 个 FutureTask,调用 get () 依次获取结果。
4. Future 的局限性
- 并发执行多任务时,仅能通过 get () 阻塞获取结果,无其他非阻塞等待方式;
- 无法对多个任务进行链式调用(如任务 A 完成后自动执行任务 B);
- 不能组合多个任务(如 10 个任务全部完成后执行后续操作);
- 无异常处理机制(接口未提供异常处理相关方法)。
二、CompletableFuture:Future 的扩展与增强
1. 核心定位
- 实现 Future 接口,弥补 Future 的局限性,核心能力是任务编排(灵活组织任务的运行顺序、规则、方式),无需 CountDownLatch 等工具类的复杂逻辑。
2. 异步任务创建:4 个静态方法
方法签名 | 返回值 | 入参特点 | 线程池 |
---|---|---|---|
runAsync(Runnable runnable) |
CompletableFuture |
无返回值(Runnable) | 默认 ForkJoinPool.commonPool () |
runAsync(Runnable runnable, Executor executor) |
CompletableFuture |
无返回值,指定线程池 | 用户指定 Executor |
supplyAsync(Supplier<U> supplier) |
CompletableFuture | 有返回值(Supplier 的 get ()) | 默认 ForkJoinPool.commonPool () |
supplyAsync(Supplier<U> supplier, Executor executor) |
CompletableFuture | 有返回值,指定线程池 | 用户指定 Executor |
- 注意:默认线程池
ForkJoinPool.commonPool()
线程数默认等于 CPU 核数(可通过 JVM 参数-Djava.util.concurrent.ForkJoinPool.common.parallelism
修改),建议按业务类型创建独立线程池避免线程饥饿。
3. 结果获取:join () vs get ()
方法 | 异常处理 | 使用场景 |
---|---|---|
join() |
抛 unchecked 异常(无需强制处理) | 不想手动处理检查异常时 |
get() |
抛 checked 异常(InterruptedException、ExecutionException,需 try-catch 或抛出) | 需显式处理异常时(推荐) |
4. 核心功能:任务编排与结果处理
功能分类 | 关键方法 | 作用说明 | 案例效果(部分) |
---|---|---|---|
结果处理 | whenComplete() | 任务完成 / 异常时执行 Action,处理结果或异常,返回原 CompletableFuture 结果 | 任务正常返回则打印 “执行完成”,异常则触发 exceptionally |
exceptionally() | 捕获任务异常,返回默认值 | 算术异常时返回 “异常 xxxx” | |
结果转换 | thenApply() | 接收上一任务结果,转换类型,返回同一 CompletableFuture | 100→300(100*3) |
thenCompose() | 接收上一结果,返回新 CompletableFuture(展开内部 Future) | 随机数 n→2n(新 CompletableFuture 计算) | |
结果消费 | thenAccept() | 消费单个结果,无返回值 | 随机数 8→打印 “40”(8*5) |
thenRun() | 不关心结果,任务完成后执行 Runnable | 打印 “thenRun 执行” | |
thenAcceptBoth() | 两个任务均完成,消费两者结果,无返回值 | 任务 1 返回 2、任务 2 返回 1→打印 “3” | |
结果组合 | thenCombine() | 合并两个任务结果,返回新结果 | 9+5=14(两个随机数相加) |
任务交互 | applyToEither() | 取两个任务中先完成的结果,转换后返回 | 任务 1 耗时 6s、任务 2 耗时 5s→取 5 并 ×2 |
acceptEither | 两个线程任务相比较,先获得执行结果的,就对该结果进行下一步的消费操作。 | ||
runAfterEither | 两个线程任务相比较,有任何一个执行完成,就进行下一步操作,不关心运行结果 | ||
runAfterBoth() | 两个任务均完成,执行 Runnable(不关心结果) | 任务 1 睡 1s、任务 2 睡 2s→打印 “均完成” | |
allOf() | 所有任务完成后返回(无返回值,需单独查每个任务结果) | future1、future2 均完成→isDone () 均为 true | |
anyOf() | 多个任务中任一完成,返回该任务结果 | future1(睡 0-5s)、future2(睡 0-1s)→返回 “world” |
5. 实战案例:烧水泡茶(对比 Future)
- 需求:最优工序为 “洗水壶(1min)→烧开水(15min)” 与 “洗茶壶(1min)→洗茶杯(2min)→拿茶叶(1min)” 并行,烧开水和拿茶叶完成后泡茶。
- Future 实现:需创建 T1Task(依赖 T2Task 的 FutureTask 获取茶叶)、T2Task,手动管理线程,代码较繁琐;
- CompletableFuture 实现:
- f1(runAsync):洗水壶→烧开水;
- f2(supplyAsync):洗茶壶→洗茶杯→拿茶叶(返回 “龙井”);
- f3(thenCombine):f1 和 f2 均完成后,执行泡茶逻辑,返回 “上茶:龙井”;
- 最终通过 f3.join () 获取结果,代码更简洁,任务依赖关系更清晰。
关键问题
问题 1:Future 相比 Runnable 有哪些核心优势?又存在哪些无法避免的局限性?
-
答案:
优势:1.结果获取:Runnable 无返回值,Future 可通过 get () 方法获取 Callable 任务的执行结果;
2.任务管理:Future 提供 cancel ()(取消任务)、isCancelled ()(查询是否取消)、isDone ()(查询是否完成)等 API,可实时掌握任务状态,而 Runnable 无此能力;
3.异常间接处理:虽然 Future 无专门异常处理方法,但 get () 会抛出 ExecutionException(封装任务执行时的异常),可间接捕获任务异常,而 Runnable 的 run () 不能抛出 checked Exception。
局限性:1.阻塞获取结果:仅能通过 get () 阻塞等待结果,无其他非阻塞方式;
2.无链式 / 组合能力:无法实现 “任务 A 完成→自动执行任务 B” 的链式调用,也不能组合多个任务(如 10 个任务全部完成后执行后续操作);
3.无异常处理机制:接口未提供专门的异常处理方法,需依赖 get () 的异常抛出间接处理,不够灵活。
问题 2:CompletableFuture 如何实现 “任务依赖关系” 的编排?请结合具体方法和案例说明。
-
答案:
CompletableFuture 通过结果转换类方法实现任务依赖(前一任务结果作为后一任务入参),核心方法为
thenApply()
和thenCompose()
,具体如下:-
thenApply():接收上一任务的结果,通过 Function 转换类型,返回同一 CompletableFuture(适用于同步转换场景)。
案例:异步任务 1 返回 100(supplyAsync (() -> 100)),通过 thenApply (number -> number * 3) 将结果转换为 300,最终 get () 获取 300,整个过程依赖同一 CompletableFuture。
-
thenCompose():接收上一任务结果,返回新的 CompletableFuture(适用于异步转换场景,可展开内部 Future,避免嵌套)。
案例:异步任务 1 生成随机数 n(supplyAsync (() -> new Random ().nextInt (30))),thenCompose (param -> CompletableFuture.supplyAsync (() -> param * 2)) 创建新 CompletableFuture 计算 2n,最终返回 2n 的结果,实现 “前一异步任务→后一异步任务” 的依赖。
-
问题 3:在 “促销活动商品信息查询” 场景中,同步查询存在什么问题?使用 Future 改造的核心优势是什么?具体如何实现?
- 答案:
- 同步查询问题:商品信息(基本信息、价格、库存、图片、销售状态)分布在不同系统,每个接口耗时约 50ms,同步查询需依次调用 5 个接口,总耗时 200-300ms,响应速度慢,影响用户体验。
- Future 改造的核心优势:将同步串行查询改为并行查询,总耗时约等于单个接口的最长耗时(约 50ms),大幅提升查询效率。
- 具体实现:
- 步骤 1:定义 5 个 Callable 任务(T1Task 查基本信息、T2Task 查价格、T3Task 查库存、T4Task 查图片、T5Task 查销售状态),每个任务 sleep 50ms 模拟接口耗时,返回查询结果;
- 步骤 2:创建 5 个 FutureTask 对象,分别封装上述 Callable 任务;
- 步骤 3:创建固定线程池(ExecutorService executorService = Executors.newFixedThreadPool (5)),提交 5 个 FutureTask 并行执行;
- 步骤 4:通过 FutureTask 的 get () 方法获取每个任务的结果,最终汇总商品信息,总耗时约 50ms。