CompletableFuture使用与解读(1)

2 使用

从类来看,其实现了CompletionStage接口以及Future接口;futrue的用法就不在这里说了,这里仅仅说明CompletionStage方法以及相关方法用法;

调用整个过程,我把它看成是个流,每次方法生成的CompletableFuture都是一个流节点,每个流有自己的完成结果,其后面的流依赖其完成后才可执行

2.1 流的产生

  • 静态方法

val ff = CompletableFuture()

  • 数据提供者Supplier

CompletableFuture.supplyAsync {
println(“create thread ${Thread.currentThread().name}”)
100
}

  • 任务事件Runnable

CompletableFuture.runAsync {
println(“create: buy meat and vegetables”)
}

  • 组合并集任务

CompletableFuture.allOf(CompletableFuture.runAsync{
println(“create: wear shoes”)
}, CompletableFuture.runAsync{
println(“create: wear dress”)
})

  • 组合互斥任务

CompletableFuture.anyOf(CompletableFuture.runAsync{
println(“create: read a book”)
}, CompletableFuture.runAsync{
println(“create: write”)
})

2.2 流的处理

流的处理方法比较多了,有37个,写代码不方便;完成方法表格如下,表格备注表达了我对这些方法的抽象与理解,看了这个,有助于更好理解下面涉及的东西

CompletableFuture详细方法.png 太多了很难记,也不好理解,下面给出了简略精华版本方法表;通过这些方法,清除明了这个类可以做到什么样得组合变换

CompletableFuture简略方法.png

下面给出几个简单事例

  1. 无组合的变化、消费

CompletableFuture.supplyAsync {
println(“create thread ${Thread.currentThread().name}”)
100
}.thenApply {
println(“map thread ${Thread.currentThread().name}”)
it * 10
}.thenAccept {
println(“consume $it”)
}

  1. 组合变化、消费

CompletableFuture.supplyAsync {
10
}.applyToEither(CompletableFuture.supplyAsync {
100
}, Function<Int, Int> {
it * 10 + 3
}).thenCombine(CompletableFuture.supplyAsync{
“Lily”
}, BiFunction<Int, String, Stu> { t, u -> Stu(u, t)}).thenAccept {
println(“name ${it.name}, age ${it.age}”)
}

  1. 异常转换、多次消费

val ff = CompletableFuture()
ff.handle{
_, _ -> 10
}.whenComplete{
t, u -> println(“first handler $t”)
}.whenComplete { t, u -> println(“second handler $t”)}
ff.obtrudeValue(null)

2.3 流结果设置

这里也通过表格方式,有下面几种方法

CompletableFuture结果设置.png

我们通过构造器生成时,需要自己设置值,如下

val ff = CompletableFuture()
ff.thenApply {
it / 2 + 4
}
ff.complete(16)

设置值后,后面的流才会执行

3. 源码解读

CompletableFuture是流的一个节点,内部持有了完成状态以及依赖其的任务节点信息,其内部同样实现了完成态时依赖任务执行处理;

3.1 数据结构

这主要体现这两个成员变量上

volatile Object result;
volatile Completion stack;

  • result:结果为null,表示未执行;执行结果为空,则设置为静态常量NIL,异常则设置为AltResult实例,正常完成,则表示实际的值; AltResult内容如下

static final AltResult NIL = new AltResult(null);
static final class AltResult {
final Throwable ex;
AltResult(Throwable x) { this.ex = x; }
}

  • stack:链表尾部指针,组成了后进先出的链表结构;是依赖当前完成状态需要执行的任务集合;内容如下,其实现ForkJoinTask,只是为了利用ForkJoinPoo线程池,其最大有点就是解决频繁的异步任务的,很配

abstract static class Completion extends ForkJoinTask
implements Runnable, AsynchronousCompletionTask {
volatile Completion next;
abstract CompletableFuture<?> tryFire(int mode);

abstract boolean isLive();
public final void run() { tryFire(ASYNC); }
public final boolean exec() { tryFire(ASYNC); return false; }
public final Void getRawResult() { return null; }
public final void setRawResult(Void v) {}
}

对于stack处理

  • postFire方法: 通知其依赖的节点,进行完成传播;由于没有使用锁,只使用了原子操作,这样可以防止,有些节点加入到依赖集合中,却不能得到执行
  • cleanStack方法:清除失活以及无效的节点
  • postComplete方法:执行stack集合中任务
  • casStack方法:改变队尾操作
  • tryPushStack方法:尝试加入队尾数据
  • pushStack:队尾加入数据

3.2 Completion以及子类

Completion类,抽象类,待执行的任务节点;其内部持有下个流以及流任务执行的逻辑;其继承关系类图如下:

Completion类图.jpg

内部变量

CompletableFuture dep;
CompletableFuture src;
CompletableFuture snd

dep代表当前操作新成的流节点,src、snd为其依赖的流节点;其中每个类,还有流任务执行的对象:Runable、Function、ConSumer、BiFunction、BiConsumer等

tryFire方法很重要,其持有的转换对象、消费对象代表了需要执行的操作;其实他们对应的tryFire方法内部实际操作,都在CompletableFuture内有对应方法

tryFire方法

很关键的方法,其持有的转换对象、消费对象代表了需要执行的操作;其情况与具体的模式有关,其情况如下

  • SYNC = 0, 同步状态;执行线程为当前方法调用线程或者上个流执行所在线程;同时其可能仅仅是为了启动线程池启动任务
  • ASYNC = 1,异步,表示需要在线程池内
    执行
  • NESTED = -1,传播模式,表示依赖的流节点已经处于完成状态,正在传递处理

claim方法

线程池任务提交,并且执行有且提交一次

3.3 中间流生成与执行原理

中间流处理,就是CompletionStage声明的方法;其系列处理方法,基本逻辑相同,也就是方法名称不同而已,而由于持有的任务不同而略有不同

3.3.1 thenRun系列

均是通过私有方法uniRunStage进行处理,进行添加时尝试处理的

private CompletableFuture uniRunStage(Executor e, Runnable f) {
if (f == null) throw new NullPointerException();
CompletableFuture d = newIncompleteFuture();
if (e != null || !d.uniRun(this, f, null)) {
UniRun c = new UniRun(e, d, this, f);
push©;
c.tryFire(SYNC);
}
return d;
}

对于此方法有下面逻辑

  1. 同步执行,且uniRun执行成功,则返回生成流节点
  2. 否则,添加相应Completion子类到等待集合中,并再次尝试执行;和之前提到的postFire结合确保一定能够执行

final boolean uniRun(CompletableFuture<?> a, Runnable f, UniRun<?> c) {
Object r; Throwable x;
if (a == null || (r = a.result) == null || f == null)
return false;
if (result == null) {
if (r instanceof AltResult && (x = ((AltResult)r).ex) != null)
completeThrowable(x, r);
else
try {
if (c != null && !c.claim())
return false;
f.run();
completeNull();
} catch (Throwable ex) {
completeThrowable(ex);
}
}
return true;
}

方法的最后一个参数,当是触发线程池提交任务操作时,需要传入任务实例,否则传入空指;也就是传入空指,代表此方法中直接执行,这时,线程可能为生成流节点方法线程,也可能是上个流节点执行的线程,也可能是线程池创建的线程中(好像等于白说了);这个方法流程如下:

  1. 检验依赖节点执行状态,未完成则结束

  2. 执行异常结束,则设置异常状态,结束

  3. 正常执行结束时,尝试执行当前任务

  • 需要向线程池提交任务,则通过claim方法,进行处理,并返回;提交任务后会执行tryFire方法
  • 不需要向线程池提交任务,执行;若执行成功,有结果直接设置结果,无结果设置NIL值;若是发生已成设置异常

如果调用CompletionStage声明的方法未能立刻执行的,则需要通过依赖的流节点完成后通过postComplete方法进行分发;

final void postComplete() {
CompletableFuture<?> f = this; Completion h; while ((h = f.stack) != null || (f != this && (h = (f = this).stack) != null)) { CompletableFuture<?> d; Completion t;
if (f.casStack(h, t = h.next)) {
if (t != null) {
if (f != this) {
pushStack(h);
continue;
}
h.next = null;
}
f = (d = h.tryFire(NESTED)) == null ? this : d;
}
}
}

tryFire方法,返回空表示流节点任务没有完成,否则表示已完成,继续这个节点的分发;也就是分发时通过tryFire方法去执行依赖节点的任务

最后

小编这些年深知大多数初中级Android工程师,想要提升自己,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助

因此我收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人

都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

资料⬅专栏获取
年深知大多数初中级Android工程师,想要提升自己,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助**。

因此我收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。

[外链图片转存中…(img-nWFDvwPm-1719051013471)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人

都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

资料⬅专栏获取

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值