CompletableFuture

CompletableFuture

为什么会有CompletableFuture

在CompletableFuture未开发出来前,使用Java进行获取异步编程的结果是很不方便的,通常使用Future这个类获取结果,但是是Future是Java 5添加的类,用来描述一个异步计算的结果。你可以使用isDone方法检查计算是否完成,或者使用get阻塞住调用线程,直到计算完成返回结果,你也可以使用cancel方法停止任务的执行。

虽然Future以及相关使用方法提供了异步执行任务的能力,但是对于结果的获取却是很不方便,只能通过阻塞或者轮询的方式得到任务的结果。阻塞的方式显然和我们的异步编程的初衷相违背,轮询的方式又会耗费无谓的CPU资源,而且也不能及时地得到计算结果,为什么不能用观察者设计模式当计算结果完成及时通知监听者呢?

所以在Java 8中, 新增加了一个包含50个方法左右的类: CompletableFuture,提供了非常强大的Future的扩展功能,可以帮助我们简化异步编程的复杂性,提供了函数式编程的能力,可以通过回调的方式处理计算结果,并且提供了转换和组合CompletableFuture的方法。

CompletableFuture实现了Future和CompletionStage两个接口。
Future接口相对简单些,提供了取消(cancel)、获取结果(get)、检测是否完成或者取消(isCancelled、isDone)这些方法,Future接口提供了异步获取结果的能力。但是Future没有提供通知机制,Future是否执行完任务需要不断的检查或者调用get()方法阻塞任务执行,所以CompletableFuture还实现了CompletionStage。CompletionStage相当于阶段,一个CompletionStage对象是异步计算中的一个阶段,当一个CompletionStage完成时会触发下一个动作或计算。前一个任务执行成功后可以自动触发下一个任务的执行,中间无需等待。相当于实现了异步任务的通知机制

CompletableFuture还能够将任务放到不同的线程中执行,既可以在当前线程中直接执行任务,也可以将其放到其他任务线程中执行,这个过程是自动的,无需干预。

使用方法

  1. CompletableFuture类实现了CompletionStageFuture接口,所以你还是可以像以前一样通过阻塞或者轮询的方式获得结果,尽管这种方式不推荐使用。
public class BasicMain {
    public static CompletableFuture<Integer> compute() {
        final CompletableFuture<Integer> future = new CompletableFuture<>();
        return future;
    }
    public static void main(String[] args) throws Exception {
        final CompletableFuture<Integer> f = compute();
        class Client extends Thread {
            CompletableFuture<Integer> f;
            Client(String threadName, CompletableFuture<Integer> f) {
                super(threadName);
                this.f = f;
            }
            @Override
            public void run() {
                try {
                    System.out.println(this.getName() + ": " + f.get());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (ExecutionException e) {
                    e.printStackTrace();
                }
            }
        }
        new Client("Client1", f).start();
        new Client("Client2", f).start();
        System.out.println("waiting");
        f.complete(100);
        //f.completeExceptionally(new Exception());
        System.in.read();
        
        //  控制台打印
        //  waiting
		//	Client1: 100
		//	Client2: 100
    }
}

可以看到get方法阻塞了client1和client2,当 f.complete(100)计算完成时,client1和client2才输出。

  1. CompletableFuture.completedFuture是一个静态辅助方法,用来返回一个已经计算好的CompletableFuture

    public static <U> CompletableFuture<U> completedFuture(U value)//value就代表线程执行run返回回去的值
    
创建异步对象

而以下四个静态方法用来为一段异步执行的代码创建CompletableFuture对象:

public static CompletableFuture<Void> 	runAsync(Runnable runnable)
public static CompletableFuture<Void> 	runAsync(Runnable runnable, Executor executor)
public static <U> CompletableFuture<U> 	supplyAsync(Supplier<U> supplier)
public static <U> CompletableFuture<U> 	supplyAsync(Supplier<U> supplier, Executor executor)

Async结尾代表异步执行

有没有Executor代表是使用接口提供给的线程池,还是自定义的

Runnable类型的参数会忽略计算的结果

Supplier类型类型的参数会返回计算的结果,U代表CompletableFuture的计算结果类型

异步执行完成的回调
public CompletableFuture<T> 	whenComplete(BiConsumer<? super T,? super Throwable> action)
public CompletableFuture<T> 	whenCompleteAsync(BiConsumer<? super T,? super Throwable> action)
public CompletableFuture<T> 	whenCompleteAsync(BiConsumer<? super T,? super Throwable> action, Executor executor)
public CompletableFuture<T>     exceptionally(Function<Throwable,? extends T> fn)

Consumer是只处理计算结果不返回处理后的值

BiConsumer会组合另外一个CompletionStage只处理计算结果,这里相当于结合上一个CompletableFuture处理结果

<? super T,? super Throwable>代表可以处理正常的计算结果,或者异常情况。 注意这几个方法都会返回`CompletableFuture`,当`Action`执行完毕后它的结果返回原始的`CompletableFuture`的计算结果或者返回异常。 `exceptionally`方法返回一个新的CompletableFuture,Function

例子:

public void testAcceptrun() throws ExecutionException, InterruptedException {
    CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
        return 100;
    });
    CompletableFuture<Void> f =  future.thenAcceptBoth(CompletableFuture.completedFuture(10), (x, y) -> System.out.println(x * y));
    System.out.println(f.get());
    f.runAfterBoth(CompletableFuture.completedFuture(10), new Runnable() {
        @Override
        public void run() {
            System.out.println("不使用组合返回的结果");
        }
    });
    //1000
    // null
    // 不使用组合返回的结果
}

#### 多异步任务的组合处理

public CompletableFuture thenCompose(Function<? super T,? extends CompletionStage> fn)
public CompletableFuture thenComposeAsync(Function<? super T,? extends CompletionStage> fn)
public CompletableFuture thenComposeAsync(Function<? super T,? extends CompletionStage> fn, Executor executor)


这一组方法接受一个Function作为参数,这个Function的输入是当前的CompletableFuture的计算值,返回结果将是一个新的CompletableFuture,这个新的CompletableFuture会组合原来的CompletableFuture和函数返回的CompletableFuture。

记住,thenCompose方法会在某个任务执行完成后,将该任务的执行结果作为方法入参然后执行指定的方法,该方法会返回一个新的CompletableFuture实例。

CompletableFuture future = CompletableFuture.supplyAsync(() -> {
return 100;
});
CompletableFuture f = future.thenCompose( i -> {
return CompletableFuture.supplyAsync(() -> {
return (i * 10) + “”;
});
});
System.out.println(f.get()); //1000


而下面的一组方法`thenCombine`用来复合另外一个CompletionStage的结果。它的功能类似:
两个CompletionStage是并行执行的,它们之间并没有先后依赖顺序,`other`并不会等待先前的`CompletableFuture`执行完毕后再执行。

public <U,V> CompletableFuture thenCombine(CompletionStage<? extends U> other, BiFunction<? super T,? super U,? extends V> fn)
public <U,V> CompletableFuture thenCombineAsync(CompletionStage<? extends U> other, BiFunction<? super T,? super U,? extends V> fn)
public <U,V> CompletableFuture thenCombineAsync(CompletionStage<? extends U> other, BiFunction<? super T,? super U,? extends V> fn, Executor executor)


其实从功能上来讲,它们的功能更类似`thenAcceptBoth`,只不过`thenAcceptBoth`是纯消费,它的函数参数没有返回值,而`thenCombine`的函数参数`fn`有返回值。

CompletableFuture future = CompletableFuture.supplyAsync(() -> {
return 100;
});
CompletableFuture future2 = CompletableFuture.supplyAsync(() -> {
return “abc”;
});
CompletableFuture f = future.thenCombine(future2, (x,y) -> y + “-” + x);
System.out.println(f.get()); //abc-100


thenCompose和thenCombine的区别:

- thenCompose 可以用于组合多个CompletableFuture,将前一个任务的返回结果作为下一个任务的参数,它们之间存在着先后顺序。

- thenCompose方法会在某个任务执行完成后,将该任务的执行结果作为方法入参然后执行指定的方法,该方法会返回一个新的CompletableFuture实例。

- thenCombine两个任务中只要有一个执行异常,则将该异常信息作为指定任务的执行结果。
- thenCombine两个任务是并行执行的,它们之间并没有先后依赖顺序。

**Either**

Either顾名思义是当任意一个CompletableFuture计算完成的时候就会执行。

public CompletableFuture acceptEither(CompletionStage<? extends T> other, Consumer<? super T> action)
public CompletableFuture acceptEitherAsync(CompletionStage<? extends T> other, Consumer<? super T> action)
public CompletableFuture acceptEitherAsync(CompletionStage<? extends T> other, Consumer<? super T> action, Executor executor)

public CompletableFuture applyToEither(CompletionStage<? extends T> other, Function<? super T,U> fn)
public CompletableFuture applyToEitherAsync(CompletionStage<? extends T> other, Function<? super T,U> fn)
public CompletableFuture applyToEitherAsync(CompletionStage<? extends T> other, Function<? super T,U> fn, Executor executor)


`accept`代表异步执行完执行消费,这个方法返回`CompletableFuture<Void>

`applyTo`方法代表转换,只是从CompletableFuture(T)-->CompletableFuture(U),CompletableFuture不变

下面这个例子有时会输出`100`,有时候会输出`200`,哪个Future先完成就会根据它的结果计算。

public void testapplyToEither() throws ExecutionException, InterruptedException {
Random rand = new Random();
CompletableFuture future = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(1000 + rand.nextInt(1000));

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return 100;
    });
    CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> {
        try {
            Thread.sleep(1000 + rand.nextInt(1000));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return 200;
    });
    CompletableFuture<String> f =  future.applyToEither(future2,i -> i.toString());
    System.out.println(f.get());//200
}

#### 辅助方法 `allOf` 和 `anyOf`

前面我们已经介绍了几个静态方法:`completedFuture`、`runAsync`、`supplyAsync`,下面介绍的这两个方法用来组合多个CompletableFuture。

public static CompletableFuture allOf(CompletableFuture<?>… cfs)
public static CompletableFuture anyOf(CompletableFuture<?>… cfs)


`allOf`方法是当所有的`CompletableFuture`都执行完后执行计算。

`anyOf`方法是当任意一个`CompletableFuture`执行完后就会执行计算,计算的结果是第一个执行完的CompletableFuture的计算结果。

Random rand = new Random();
CompletableFuture future1 = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(10000 + rand.nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
return 100;
});
CompletableFuture future2 = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(10000 + rand.nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
return “abc”;
});
//CompletableFuture f = CompletableFuture.allOf(future1,future2);
CompletableFuture f = CompletableFuture.anyOf(future1,future2);
System.out.println(f.get());//100

原理

下面以如下代码为例介绍CompletableFuture的实现原理。

public static void main(String[] args) throws Exception {
    CompletableFuture f=CompletableFuture.supplyAsync(()->{
        System.out.println("实验");
        return "1";
    }).thenAccept((x)->{
        System.out.println(x);
    });
}

先来看一下静态方法supplyAsync():

public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier) {
	//asyncPool是一个线程池,
	//它根据配置或者CPU的个数来决定是使用ForkJoinPool还是ThreadPerTaskExecutor作为线程池的实现
    return asyncSupplyStage(asyncPool, supplier);
}
static <U> CompletableFuture<U> asyncSupplyStage(Executor e,
                                                 Supplier<U> f) {
    if (f == null) throw new NullPointerException();
    //创建一个新的CompletableFuture对象,所以后面调用thenAccept()方法其实使用的是这个新建的对象
    CompletableFuture<U> d = new CompletableFuture<U>();
    //在线程池中执行AsyncSupply异步任务
    e.execute(new AsyncSupply<U>(d, f));
    return d;
}

CompletableFuture提供了属性asyncPool记录线程池对象,当调用静态方法时,CompletableFuture都是使用该线程池来执行异步任务,如果自己传了线程池,则使用自定义的线程池
asyncSupplyStage()方法返回一个新建的CompletableFuture对象,在CompletableFuture中大部分public方法都会返回一个新建的CompletableFuture对象,所以在链式调用中每次调用的方法都是新建对象的方法,而不是最初那个对象的。
最后将AsyncSupply()对象放入了线程池中,下面来看一下AsyncSupply.run()方法的实现:

public void run() {
CompletableFuture d; Supplier f;
//dep是在asyncSupplyStage()方法中新建的那个CompletableFuture对象
//fn是自定义的Supplier对象
if ((d = dep) != null && (f = fn) != null) {
dep = null; fn = null;
//CompletableFuture的result属性表示Supplier对象的执行结果,
//更准确的说,result属性是用来记录我们编写的lambda表达式的运算结果的
if (d.result == null) {
try {
//使用CAS将Supplier的执行结果放入到属性result中
d.completeValue(f.get());
} catch (Throwable ex) {
//如果抛出异常,将异常对象记录到属性result
d.completeThrowable(ex);
}
}
//下面详细介绍postComplete()方法
d.postComplete();
}

//postComplete()方法的作用是检查是否有下一个任务需要执行,如果需要便会触发该任务的执行
//每调用一个CompletableFuture实例方法,CompletableFuture都将该实例方法要执行的任务封装成一个Completion对象,
//并将Completion对象压入到当前CompletableFuture对象的栈中(栈顶使用stack属性记录),
//也就是将后一个CompletableFuture对象的任务压入到前一个对象的栈中,
//每次执行完当前任务后,都会调用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;
        //casStack()将栈顶切换为下一个次栈顶元素
        if (f.casStack(h, t = h.next)) {
            if (t != null) {
                if (f != this) {
                	//当后一个CompletableFuture对象的栈中的任务需要嵌入到当前栈中执行时,
                	//postComplete()获取到这些任务并调用pushStack()放入自己的栈中,
                	//然后在本方法里面执行这些任务
                    pushStack(h);
                    continue;
                }
                h.next = null;    // detach
            }
            //如果栈中有待执行任务,调用tryFire()方法执行该任务,
            //如果tryFire()的入参为NESTED(值为-1),且返回的不是null,
            //说明后一个CompletableFuture对象的栈中的任务需要嵌入执行
            f = (d = h.tryFire(NESTED)) == null ? this : d;
        }
    }
}

run()方法执行了当前Supplier对象,并记录下执行结果。之后调用postComplete()检查栈顶是否有待执行任务,后面介绍thenAccept()方法的实现原理时,我们可以看到任务是如何被放到栈中的。这里还有一点需要注意,调用tryFire()方法时,如果入参为NESTED,表示后一个CompletableFuture的栈中的任务需要嵌入到当前线程中(或者说是嵌入到当前栈中)执行,这相当于当前线程执行完了所有任务后,检查到后一个对象的栈中还有任务,那么便“偷”一个任务过来执行。
tryFire()有多个不同的实现,这里不再一一介绍源码。
下面来看一下thenAccept()方法的实现原理。

public CompletableFuture<Void> thenAccept(Consumer<? super T> action) {
    return uniAcceptStage(null, action);
}
private CompletableFuture<Void> uniAcceptStage(Executor e,
                                               Consumer<? super T> f) {
    if (f == null) throw new NullPointerException();
    //新建一个CompletableFuture对象d,本方法将该对象返回给调用方
    CompletableFuture<Void> d = new CompletableFuture<Void>();
    //uniAccept()检查前一个任务是否执行完成,如果执行成功了,那么执行Consumer对象
    //注意:入参this指的是supplyAsync()方法里面创建的CompletableFuture对象
    //这里逻辑比较绕,一定分清楚现在调用的thenAccept()方法是属于是supplyAsync()方法里面创建的CompletableFuture对象的
    if (e != null || !d.uniAccept(this, f, null)) {
        UniAccept<T> c = new UniAccept<T>(e, d, this, f);
        //将UniAccept对象压入到CompletableFuture对象的栈中
        //这个栈是supplyAsync()方法里面创建的CompletableFuture对象的
        //push()方法使用CAS将对象c压入到栈
        push(c);
        c.tryFire(SYNC);
    }
    return d;
}
final <S> boolean uniAccept(CompletableFuture<S> a,
                            Consumer<? super S> f, UniAccept<S> c) {
    Object r; Throwable x;
    //a.result表示上一个任务的执行结果,如果为null,表示任务还在执行中
    //上一个任务没有执行完,直接退出当前方法
    if (a == null || (r = a.result) == null || f == null)
        return false;
    //result = null表示当前任务还没执行
    //能够执行下面的逻辑,说明上一个任务执行完毕,可以执行当前任务了
    tryComplete: if (result == null) {
        if (r instanceof AltResult) {
            if ((x = ((AltResult)r).ex) != null) {
                completeThrowable(x, r);
                break tryComplete;
            }
            r = null;
        }
        try {
            if (c != null && !c.claim())
                return false;
            @SuppressWarnings("unchecked") S s = (S) r;
            //执行Consumer任务
            f.accept(s);
            //向result属性设置结果
            completeNull();
        } catch (Throwable ex) {
            completeThrowable(ex);
        }
    }
    return true;
}

thenAccept()方法首先检查前一个CompletableFuture的任务是否执行完成,如果没有,则创建UniAccept对象,并将该对象压入栈中,如果执行完成,则执行Consumer任务,并设置执行结果。设置执行结果使用completeNull()方法,该方法通过CAS将NIL对象设置到result属性,NIL表示的是空结果,当任务返回null或者任务没有执行结果时,都会设置result=NIL

static final AltResult NIL = new AltResult(null);

在上面代码中,UniAccept对象压入栈之后,便调用UniAccept.tryFire()方法,下面来看一下该方法的实现原理:

final CompletableFuture<Void> tryFire(int mode) {
    CompletableFuture<Void> d; CompletableFuture<T> a;
    //dep表示thenAccept()方法中新建的CompletableFuture对象d
    //src表示supplyAsync()方法中创建的CompletableFuture对象
    if ((d = dep) == null ||
    	//uniAccept()在上面已经介绍过了
    	//这里调用uniAccept()方法可以确保在多线程环境下,Consumer对象已经可以被执行
        !d.uniAccept(a = src, fn, mode > 0 ? null : this))
        return null;
    dep = null; src = null; fn = null;
    //postFire()做收尾工作,检查对象a和对象d的栈是否有待执行任务,如果有分别调用postComplete()方法,
    //在调用前还会检查mode的值,如果mode为NESTED(-1),则说明栈的任务由别的线程执行,不再执行
    return d.postFire(a, mode);
}
final CompletableFuture<T> postFire(CompletableFuture<?> a, int mode) {
    if (a != null && a.stack != null) {
        if (mode < 0 || a.result == null)
            a.cleanStack();//清理栈,将执行过的任务从栈中清除
        else
            a.postComplete();//执行栈的任务,就本小节的例子来看,a栈的任务都已经执行过了
    }
    if (result != null && stack != null) {
        if (mode < 0)
            return this;//直接返回当前CompletableFuture对象,该对象的栈任务由其他线程执行
        else
            postComplete();//执行栈任务
    }
    return null;
}

在这里插入图片描述

到这里本小节示例代码的执行过程介绍完毕,下面总结一下。
每个CompletableFuture的方法都会新建CompletableFuture对象,并将当前方法需要执行的任务封装成Completion对象,之后将Completion压入到上个方法中创建的CompletableFuture对象的栈中,这样每个CompletableFuture对象的栈都保存了下一个待执行的任务,通过栈将每个任务串在一起,形成一个链条。当执行完当前CompletableFuture对象的任务后,接着查看栈中是否有任务,如果有则直接执行,如果没有就退出。后一个CompletableFuture对象也是一样,首先将任务压入前一个对象的栈中,检查前一个任务是否执行完成,如果没有则退出,如果前一个任务执行完成,则查看栈中的任务,然后执行当前任务

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值