前语

本文从归于我归纳整理的Android常识体系的第四部分,归于 异步 部分的多线程内容

您能够经过访问 总纲 阅览系列内的其他文章。

掌握CompletableFuture,驾驭异步编程

作者按:草稿进行了几回大改,移除了Demo部分、源码解析部分、规划原理部分。结合实际工作经验,”掌握API能娴熟运用、能无障碍阅览相关结构源码” 已根本够用。

读者可结合下面的导图进行快速的常识自查

掌握CompletableFuture,驾驭异步编程

一个美好的期望

通常情况下,咱们期望代码的履行次序和代码的安排次序一致,即代码表述了同步履行的程序,这样能够减少很多思考。

阅览异步的程序代码,需求在脑海中树立事件流,当程序事务复杂时,将应战人的记忆力和空间想象力,并非所有人都擅长在脑海中构建并分析异步事件流模型

所以,咱们期望拥有一个非常友好的结构,能够让咱们方便地进行异步编程,而且在结构内部规划有线程同步、反常处理机制。

而且,基于该结构编写的代码具有很高的可读、可理解性。

而Future根本无法满意这一期望。

Future的缺乏与CompletableFuture的来历

Future的缺乏

在先前的系列文章中,咱们现已回顾了Future类的规划,在绝大多数场景下,咱们挑选运用多线程,是为了 充分运用机器性能 以及 防止用户交互线程呈现长期堵塞 以致影响体会。

所以咱们将耗时的、会引起长期堵塞的使命分离到其他线程履行,并在 适宜机遇 进行线程同步,于主线程(一般担任用户交互处理、界面烘托)中处理成果。

详见拙作 掌握Future,轻松获取异步使命成果 、链接

FutureJava 1.5版本引入,它相似于 异步处理的成果占位符 , 供给了两个办法获取成果:

  • get(), 调用线程进入堵塞直至得到成果或许反常。
  • get(long timeout, TimeUnit unit), 调用线程将仅在指定时间 timeout 内等待成果或许反常,假如超时未取得成果就会抛出 TimeoutException 反常。

Future 能够完结 RunnableCallable 接口来定义使命,必定程度上满意 运用结构进行异步编程 的期望,但经过全体源码可知它存在如下 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特性,经过回调运用非堵塞办法,提升了异步编程模型。

掌握CompletableFuture,驾驭异步编程

它解决了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 中的 parallelStreamCompletableFuture 默许从全局 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 的不同

回调&链式调用

CompletableFutureget()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较为简略,不再代码演示

明显,经过链式调用能够拼装多个履行过程。

有读者或许会疑问:FunctionConsumer 也能够进行链式拼装,是否存在冗余呢?

两种的链式调用特性的确存在重叠,您能够自行挑选用法,但 thenRun 只能选用 CompletableFuture的链式调用。

别的,前面说到,咱们能够指定线程池履行使命,关于这三组API,相同有相同的特性,经过 thenXXXXAsync 指定线程池,这是 FunctionConsumer 的链式拼装所无法完结的。

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 回来逻辑中供给的 CompletableFuturethenApply 回来结构内处理的新实例。

留意,这一特性在运用 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表现如 thenApplyEither 的组合,两个同类型的 CompletableFuture 恣意一个取得成果后,可消费该成果并进行改动,相似 thenApply
  • acceptEither 系列API表现如 thenAcceptEither 的组合,两个同类型的 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 节点选用 handleexceptionally API转换反常 _

除前文说到的 handle whenCompleteCompletableFuture 中还供给了 exceptionally API用于处理反常

CompletableFuture<T> exceptionally(Function<Throwable, ? extends T> fn)

从表现成果看,它相似于 handle API中对反常的处理,将反常转换为目标成果的一种特定情形。