前语
本文从归于我归纳整理的Android常识体系的第四部分,归于 异步
部分的多线程内容
您能够经过访问 总纲 阅览系列内的其他文章。
作者按:草稿进行了几回大改,移除了Demo部分、源码解析部分、规划原理部分。结合实际工作经验,”掌握API能娴熟运用、能无障碍阅览相关结构源码” 已根本够用。
读者可结合下面的导图进行快速的常识自查
一个美好的期望
通常情况下,咱们期望代码的履行次序和代码的安排次序一致,即代码表述了同步履行的程序,这样能够减少很多思考。
而 阅览异步的程序代码,需求在脑海中树立事件流,当程序事务复杂时,将应战人的记忆力和空间想象力,并非所有人都擅长在脑海中构建并分析异步事件流模型。
所以,咱们期望拥有一个非常友好的结构,能够让咱们方便地进行异步编程,而且在结构内部规划有线程同步、反常处理机制。
而且,基于该结构编写的代码具有很高的可读、可理解性。
而Future根本无法满意这一期望。
Future的缺乏与CompletableFuture的来历
Future的缺乏
在先前的系列文章中,咱们现已回顾了Future类的规划,在绝大多数场景下,咱们挑选运用多线程,是为了 充分运用机器性能 以及 防止用户交互线程呈现长期堵塞 以致影响体会。
所以咱们将耗时的、会引起长期堵塞的使命分离到其他线程履行,并在 适宜机遇 进行线程同步,于主线程(一般担任用户交互处理、界面烘托)中处理成果。
详见拙作 掌握Future,轻松获取异步使命成果 、链接
Future
于 Java 1.5版本引入,它相似于 异步处理的成果占位符
, 供给了两个办法获取成果:
-
get()
, 调用线程进入堵塞直至得到成果或许反常。 -
get(long timeout, TimeUnit unit)
, 调用线程将仅在指定时间 timeout 内等待成果或许反常,假如超时未取得成果就会抛出 TimeoutException 反常。
Future
能够完结 Runnable
或 Callable
接口来定义使命,必定程度上满意 运用结构进行异步编程
的期望,但经过全体源码可知它存在如下 3个问题 :
- 调用
get()
办法会一直堵塞直到获取成果、反常,无法在使命完结时取得 “告诉” ,无法附加回调函数 - 不具备链式调用和成果聚合处理才能,当咱们想链接多个
Future
共同完结一件使命时,没有结构级的处理,只能编写事务级逻辑,兼并成果,并小心的处理同步 - 需求独自编写反常处理代码
运用 get(long timeout, TimeUnit unit)
和 isDone()
判断,的确能够缓解问题1,但这需求结合事务独自规划(调优),存在很多的不确定性。不再打开
Java 8中引入 CompletableFuture
来解决 Future
的缺乏。
CompletableFuture来历
CompletableFuture
的规划灵感来自于 Google Guava
库的 ListenableFuture
类,它完结了 Future接口
和 CompletionStage接口
,
而且新增一系列API,支撑Java 8的 lambda特性
,经过回调运用非堵塞办法,提升了异步编程模型。
它解决了Future的缺乏,答应咱们在非主线程中运转使命,并向发动线程 (一般是主线程) 告诉 使命完结
或 使命失败
,编写异步的、非堵塞的程序。
运用CompletableFuture
最简方法获取实例
运用 CompletableFuture.completedFuture(U value)
能够获取一个 履行状态现已完结
的 CompletableFuture
目标。
这能够用于快速改造旧程序,并进行逐渐过渡
class Demo {
@Test
public void testSimpleCompletableFuture() {
CompletableFuture<String> completableFuture =
CompletableFuture.completedFuture("testSimpleCompletableFuture");
assertTrue(completableFuture.isDone());
try {
assertEquals("testSimpleCompletableFuture", completableFuture.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
改造线程同步部分
部分老旧程序现已树立了多线程事务模型,咱们能够运用 CompletableFuture
改造其间的线程同步部分,但暂不改造数据传递。
运用 runAsync()
办法,该办法接纳一个 Runnable
类型的参数回来 CompletableFuture<Void>
:
//并不改动原项目中数据传递的部分、或许不关怀成果数据,仅进行同步
class Demo {
@Test
public void testCompletableFutureRunAsync() {
AtomicInteger variable = new AtomicInteger(0);
CompletableFuture<Void> runAsync = CompletableFuture.runAsync(() -> process(variable));
runAsync.join();
assertEquals(1, variable.get());
}
public void process(AtomicInteger variable) {
System.out.println(Thread.currentThread() + " Process...");
variable.set(1);
}
}
进一步改造成果数据传递
当咱们关怀异步使命的成果数据、或许改造原 多线程事务模型 的 数据传递方法 时,能够运用 supplyAsync()
办法,该办法接纳一个 Supplier<T>
接口类型的参数,它完结了使命的逻辑,办法回来 CompletableFuture<T>
实例。
class Demo {
@Test
public void testCompletableFutureSupplyAsync() {
CompletableFuture<String> supplyAsync =
CompletableFuture.supplyAsync(this::process);
try {
// Blocking
assertEquals("testCompletableFutureSupplyAsync", supplyAsync.get());
} catch (ExecutionException | InterruptedException e) {
e.printStackTrace();
}
}
public String process() {
return "testCompletableFutureSupplyAsync";
}
}
指定履行线程池
“获取用于履行使命的线程” 相似 Java 8 中的 parallelStream
, CompletableFuture
默许从全局
ForkJoinPool.commonPool()
获取线程,用于履行使命。同时也供给了指定线程池的方法用于获取线程履行使命,您能够运用API中具有 Executor
参数的重载办法。
class Demo {
@Test
public void testCompletableFutureSupplyAsyncWithExecutor() {
ExecutorService newFixedThreadPool =
Executors.newFixedThreadPool(2);
CompletableFuture<String> supplyAsync = CompletableFuture.supplyAsync(this::process,
newFixedThreadPool);
try {
// Blocking
assertEquals("testCompletableFutureSupplyAsyncWithExecutor", supplyAsync.get());
} catch (ExecutionException | InterruptedException e) {
e.printStackTrace();
}
}
public String process() {
return "testCompletableFutureSupplyAsyncWithExecutor";
}
}
CompletableFuture
中有众多API,办法命名中含有 Async
的API可运用线程池。
到此处,以上运用方法均与 Future
相似,接下来演示 CompletableFuture
的不同
回调&链式调用
CompletableFuture
的 get()
API是堵塞式获取成果,CompletableFuture
供给了
thenApply
thenAccept
thenRun
等API来防止堵塞式获取,而且可添加 使命完结
后的回调。这几个办法的运用场景如下:
-
<U> CompletableFuture<U> thenApply(Function<? super T,? extends U> fn)
收到成果后,能够进行转化 -
CompletableFuture<Void> thenAccept(Consumer<? super T> action)
收到成果后,对其进行消费 -
CompletableFuture<Void> thenRun(Runnable action)
收到成果后,履行回调,无法消费成果只能消费 这一事件
API较为简略,不再代码演示
明显,经过链式调用能够拼装多个履行过程。
有读者或许会疑问:
Function
和Consumer
也能够进行链式拼装,是否存在冗余呢?
两种的链式调用特性的确存在重叠,您能够自行挑选用法,但 thenRun
只能选用 CompletableFuture
的链式调用。
别的,前面说到,咱们能够指定线程池履行使命,关于这三组API,相同有相同的特性,经过 thenXXXXAsync
指定线程池,这是 Function
和 Consumer
的链式拼装所无法完结的。
class Demo {
@Test
public void testCompletableFutureApplyAsync() {
ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(2);
ScheduledExecutorService newSingleThreadScheduledExecutor = Executors.newSingleThreadScheduledExecutor();
// 从线程池 newFixedThreadPool 获取线程履行使命
CompletableFuture<Double> completableFuture =
CompletableFuture.supplyAsync(() -> 1D, newFixedThreadPool)
.thenApplyAsync(d -> d + 1D, newSingleThreadScheduledExecutor)
.thenApplyAsync(d -> d + 2D);
Double result = completableFuture.join();
assertEquals(4D, result);
}
}
聚合多个CompletableFuture
经过 聚合
多个 CompletableFuture
,能够组成更 复杂
的事务流,能够到达精细地操控粒度、聚集单个节点的事务。
留意:操作符并不能彻底的操控 CompletableFuture
使命履行的机遇,您需求慎重的挑选 CompletableFuture
的创建机遇
thenCompose、thenComposeAsync
compose
原意为 组成
, 经过多个 CompletableFuture
构建异步流。
在操作的 CompletableFuture
取得成果时,将另一个 CompletableFuture
compose
到异步流中,compose的过程中,能够根据操作的 CompletableFuture
的成果编写逻辑。
与 thenApply
比较,thenCompose
回来逻辑中供给的 CompletableFuture
而 thenApply
回来结构内处理的新实例。
留意,这一特性在运用 FP编程范式
进行编码时,会显得非常灵敏,必定程度上提升了函数的复用性
API含义直观,不再进行代码演示
thenCombine、thenCombineAsync
thenCombine
能够用于兼并多个 独立使命 的处理成果。
留意:
thenCompose
进行聚合时,下流能够运用上游的成果,在事务需求上一般表现为依赖上一步成果,而非两者相互独立。
例如,产品期望在博客概况页同时展示 “博客的概况” 和 “作者主要信息” ,以防止内容区颤动或分裂的骨架占位。这两者 能够独立获取时 ,则能够运用 thenCombine
系列API,别离获取,并兼并成果。
combine
的特点是 被兼并的两个 CompletableFuture
能够并发,等两者都取得成果后进行兼并。
但它仍旧存在运用上的不便捷,兼并超越2个 CompletableFuture
时,显得不行灵敏。能够运用
static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs)
API。
allOf
创建了 CompletableFuture<Void>
,并不会帮助咱们兼并成果,所以需求自行编写事务代码兼并,故存在 Side Effects
。
runAfterBoth、runAfterBothAsync;runAfterEither、runAfterEitherAsync
-
runAfterBoth
系列API在两个CompletableFuture
都取得成果后履行回调 -
runAfterEither
系列API在两个CompletableFuture
恣意一个取得成果后履行回调
经过API,不难理解它们需求运用者自行处理成果
-
CompletableFuture<Void> runAfterBoth(CompletionStage<?> other, Runnable action)
; CompletableFuture<Void> runAfterEither(CompletionStage<?> other, Runnable action)
相同能够添加编码灵敏性,不再赘述。
applyToEither、applyToEitherAsync;acceptEither、acceptEitherAsync;thenAcceptBoth、thenAcceptBothAsync
-
applyToEither
系列API表现如thenApply
和Either
的组合,两个同类型的CompletableFuture
恣意一个取得成果后,可消费该成果并进行改动,相似 thenApply -
acceptEither
系列API表现如thenAccept
和Either
的组合,两个同类型的CompletableFuture
恣意一个取得成果后,可消费该成果,相似 thenAccept -
thenAcceptBoth
系列API表现如thenCombine
,但回来CompletableFuture<Void>
相同能够添加编码灵敏性,不再赘述
成果处理
运用回调处理成果有两种API,留意,除了正常取得成果外还或许取得反常,而这两组API簇差异表现在对 反常
的处理中。
<U> CompletableFuture<U> handle(BiFunction<? super T, Throwable, ? extends U> fn)
CompletableFuture<T> whenComplete(BiConsumer<? super T, ? super Throwable> action)
handle
运用 BiFunction
,无论是正常成果还是反常情况,均视作可被逻辑接受,消费后转化
而 whenComplete
运用 BiConsumer
,仅可消费但不能转化,反常情况被视作不可被逻辑接受,仍会抛出。
举个比如,进行网络编程时会遇到 Exception
, 假如事务规划中运用的模型实体包含了 正常成果
、反常
两种情况:
open class Result<T>(val t: T?) {
open val isThr: Boolean = false
}
class FailResult<T>(val tr: Throwable) : Result<T>(null) {
override val isThr: Boolean = true
}
则适合运用 handle
API在底层处理。否则需求额外的反常处理,可根据项目的规划挑选处理方法,一般在根据FP范式规划的程序中,倾向于运用handle,防止添加side effect。
反常处理
在多线程布景下,反常处理并不容易。它不仅仅是运用 try-catch
捕获反常,还包含程序异步流中,节点呈现反常时流的事务走向。
在 CompletableFuture
中,节点呈现反常将跳过后续节点,进入反常处理。
_假如您不期望某个节点抛出反常导致后续流程中断,则可在节点的处理中捕获并包装为成果、或许对子 CompletableFuture 节点选用 handle
、exceptionally
API转换反常 _
除前文说到的 handle
whenComplete
,CompletableFuture
中还供给了 exceptionally
API用于处理反常
CompletableFuture<T> exceptionally(Function<Throwable, ? extends T> fn)
从表现成果看,它相似于 handle
API中对反常的处理,将反常转换为目标成果的一种特定情形。