JDK8探险——CompletableFuture

来源:互联网 发布:淘宝把买家加入黑名单 编辑:程序博客网 时间:2024/05/16 19:32

Java的线程不只是Thread,调度不止是.start、.join、.sleep。
jdk5引入Future、Callable,多线程调度有了很多高级应用。JDK8开始之后引入lambda,程序调度流程粒度进一步缩小,配合新的线程相关API,出现了很多看起来优雅,功能强大,使用要求高的线程调度用法。
CompletableFuture 方法探析。

1. supplyAsync

相关方法

public <U> CompletableFuture<U> thenApply(Function<? super T,? extends U> fnpublic <U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn)public <U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn, Executor executor) 

CompletableFuture的静态方法,接受一个Function生成一个新的CompletableFuture对象,对象生成时异步执行传入的Function;< U >泛型为接受的Function的返回值类型。

查看源码如下:

public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier) {    return asyncSupplyStage(asyncPool, supplier);}

指定默认的线程池asyncPool调用了asyncSupplyStage方法返回CompletableFuture,asyncPool是CompletableFuture私有静态属性。supplyAsync有一个重载方法,可以指定执行的连接池。【supplyAsync(Supplier< U > supplier, Executor executor)】
关于asyncSupplyStage方法如下:

static <U> CompletableFuture<U> asyncSupplyStage(Executor e,Supplier<U> f){    if (f == null) throw new NullPointerException();    CompletableFuture<U> d = new CompletableFuture<U>();    e.execute(new AsyncSupply<U>(d, f));    return d;}

初始化一个CompletableFuture< U >对象,和传入的Function一同包装成一个AsyncSupply对象,根据继承关系,AsyncSupply是Runnable接口的实现类。在AsyncSupply重写run方法,作用就是将Fanction结果的返回值设置到CompletableFuture对象中,完成后继续下一个任务。

public void run() {      CompletableFuture<T> d; Supplier<T> f;      if ((d = dep) != null && (f = fn) != null) {          dep = null; fn = null;          if (d.result == null) {              try {                  d.completeValue(f.get());              } catch (Throwable ex) {                  d.completeThrowable(ex);              }          }          d.postComplete();       }  }

综上所知:asyncSupplyStage方法中e.execute执行时会初始化一个Thread执行操作,传入的Function是异步执行不阻塞主线程。测试代码如下:

@Testpublic void supplyAsync() {    CompletableFuture.supplyAsync(() -> {        try {            Thread.sleep(1000);        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println("1 Say hello!" + Thread.currentThread());        return "hello";    });    CompletableFuture.supplyAsync(() -> {        System.out.println("2 Say hello!" + Thread.currentThread());        return "hello";    });    try {        Thread.sleep(2000);    } catch (InterruptedException e) {        e.printStackTrace();    }}

运行结果:.

2 Say hello!Thread[ForkJoinPool.commonPool-worker-2,5,main]1 Say hello!Thread[ForkJoinPool.commonPool-worker-1,5,main]

2.thenApplyAsync,变换

相关方法

public <U> CompletionStage<U> thenApply(Function<? super T,? extends U> fn);public <U> CompletionStage<U> thenApplyAsync(Function<? super T,? extends U> fn);public <U> CompletionStage<U> thenApplyAsync(Function<? super T,? extends U> fn,Executor executor);

使用示例:

@Testpublic void thenApply() throws InterruptedException, ExecutionException {    CompletableFuture<String> supplyAsync = CompletableFuture.supplyAsync(() -> "12");    CompletableFuture<Integer> thenApply = supplyAsync.thenApplyAsync(s -> {        System.out.println("String:" + s);        return Integer.valueOf(s);    });    Integer result = thenApply.get();    System.out.println("int:" + result);}

方法的入参fn是一个函数式接口,这个fn的入参是上一步的计算结果“12”,get()会阻塞主线程等待执行结果返回,最终打印结果:

String:12int:12

3.thenAcceptAsync,消耗

相关方法

public CompletionStage<Void> thenAccept(Consumer<? super T> action);public CompletionStage<Void> thenAcceptAsync(Consumer<? super T> action);public CompletionStage<Void> thenAcceptAsync(Consumer<? super T> action,Executor executor);

从方法名含义上理解,接受上一个执行结果,应用一个没有返回值(Void)的Consumer。
测试用例:

@Testpublic void thenAccept() {    CompletableFuture.supplyAsync(() -> "hello").thenAcceptAsync(s -> {        System.out.println(s + " world");    });}

虽然入参的Consumer对象是不期待返回值的,但是thenAcceptAsync方法是有返回值的。

4.thenRunAsync

相关方法

public CompletionStage<Void> thenRun(Runnable action);public CompletionStage<Void> thenRunAsync(Runnable action);public CompletionStage<Void> thenRunAsync(Runnable action,Executor executor);

在上一步执行结束之后,启动的一个线程,入参是一个Runnable类型的,不需要入参和返回值。和thenAccept相比,不需要之前执行结果的依赖。
测试方法

@Testpublic void thenRun() {    CompletableFuture.supplyAsync(() -> {        try {            System.out.println(Thread.currentThread());            Thread.sleep(1000);        } catch (InterruptedException e) {            e.printStackTrace();        }        return "hello";    }).thenRun(() -> {        System.out.println("hello world in " + Thread.currentThread());        System.exit(1);    });    while (true) {        System.out.println("while");        try {            Thread.sleep(100);        } catch (InterruptedException e) {            e.printStackTrace();        }    }}

输出结果:

Thread[ForkJoinPool.commonPool-worker-1,5,main]whilewhilewhilewhilewhilewhilewhilewhilewhilewhilehello world in Thread[ForkJoinPool.commonPool-worker-1,5,main]

从结果上看,Runnable 使用的线程池和supplyAsync应用的是同一个。

5.thenCombine,结合

相关方法:

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

获取上一个的返回结果,结合other的返回结果,两个结果作为参数共同传入BiFunction中计算一个最终结果。
测试方法:

@Testpublic void thenCombine() {    String result = CompletableFuture.supplyAsync(() -> {        try {            System.out.println("hello in " + Thread.currentThread());            Thread.sleep(2000);        } catch (InterruptedException e) {            e.printStackTrace();        }        return "hello";    }).thenCombine(CompletableFuture.supplyAsync(() -> {        try {            System.out.println("world in " + Thread.currentThread());            Thread.sleep(3000);        } catch (InterruptedException e) {            e.printStackTrace();        }        return "world";    }), (s1, s2) -> {        System.out.println("hello world in " + Thread.currentThread());        return s1 + " " + s2;    }).join();    System.out.println("main get result:"+result);}

执行结果:

hello in Thread[ForkJoinPool.commonPool-worker-1,5,main]world in Thread[ForkJoinPool.commonPool-worker-2,5,main]hello world in Thread[ForkJoinPool.commonPool-worker-2,5,main]main get result:hello world

分任务在不同的线程里并行执行,第二个字任务和汇总任务因为都是thenCombine的入参,在同一个线程中进行。

6.thenAcceptBothAsync

相关方法:

public <U> CompletionStage<Void> thenAcceptBoth(CompletionStage<? extends U> other,BiConsumer<? super T, ? super U> action);public <U> CompletionStage<Void> thenAcceptBothAsync(CompletionStage<? extends U> other,BiConsumer<? super T, ? super U> action);public <U> CompletionStage<Void> thenAcceptBothAsync(CompletionStage<? extends U> other,BiConsumer<? super T, ? super U> action,     Executor executor);

thenCombine的无返回值版本。

7.runAfterBoth

相关方法:

public CompletionStage<Void> runAfterBoth(CompletionStage<?> other,Runnable action);public CompletionStage<Void> runAfterBothAsync(CompletionStage<?> other,Runnable action);public CompletionStage<Void> runAfterBothAsync(CompletionStage<?> other,Runnable action,Executor executor);

thenAcceptBothAsync的无入参版。不关注上一个和other的执行结果。

8.applyToEither,选一个快的

相关方法:

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

测试方法:

@Testpublic void applyToEither() {    String result = CompletableFuture.supplyAsync(() -> {        int sleep =  (int)(Math.random()*10);        System.out.println(String.format("hello sleep %s s", sleep));        try {            Thread.sleep(sleep*1000);        } catch (InterruptedException e) {            e.printStackTrace();        }        return "hello";    }).applyToEither(CompletableFuture.supplyAsync(() -> {        int sleep =  (int)(Math.random()*10);        System.out.println(String.format("hello world %s s", sleep));        try {            Thread.sleep(sleep*1000);        } catch (InterruptedException e) {            e.printStackTrace();        }        return "world";    }), s -> s).join();    System.out.println("main get result:"+result);}

整体逻辑是,最终方法需要一个入参去计算结果,获得这个参数有两种途径,哪个快就用哪个的计算结果作为参数计算最终结果,慢的被丢弃。

测试输出结果:

hello sleep 9 shello world 3 smain get result:world

9.acceptEither & runAfterEither

同样的,applyToEither的Void版acceptEither和无参Void版runAfterEither。
相关方法:

public CompletionStage<Void> acceptEither(CompletionStage<? extends T> other,Consumer<? super T> action);public CompletionStage<Void> acceptEitherAsync(CompletionStage<? extends T> other,Consumer<? super T> action);public CompletionStage<Void> acceptEitherAsync(CompletionStage<? extends T> other,Consumer<? super T> action,Executor executor);public CompletionStage<Void> runAfterEither(CompletionStage<?> other,Runnable action);public CompletionStage<Void> runAfterEitherAsync(CompletionStage<?> other,Runnable action);public CompletionStage<Void> runAfterEitherAsync(CompletionStage<?> other,Runnable action,Executor executor);

10. exceptionally,异常补偿

相关方法

public CompletionStage<T> exceptionally(Function<Throwable, ? extends T> fn);

代码发生异常时,进行补偿,类似try-catch的功能。
测试方法:

@Testpublic void exceptionally() {    String result = CompletableFuture.supplyAsync(() -> {        try {            System.out.println(Thread.currentThread());            Thread.sleep(3000);        } catch (InterruptedException e) {            e.printStackTrace();        }        if (1 == 1) {            throw new RuntimeException("测试一下异常情况");        }        return "s1";    }).exceptionally(e -> {        System.out.println(Thread.currentThread());        System.out.println(e.getMessage());        return "hello world";    }).join();    System.out.println(result);}

输出结果:

Thread[ForkJoinPool.commonPool-worker-1,5,main]Thread[ForkJoinPool.commonPool-worker-1,5,main]java.lang.RuntimeException: 测试一下异常情况hello world

从打印线程信息看,异常处理部分的方法执行和发生异常的代码在同一个线程中执行。

11. whenComplete 结束记录。

相关方法:

public CompletionStage<T> whenComplete(BiConsumer<? super T, ? super Throwable> action);public CompletionStage<T> whenCompleteAsync(BiConsumer<? super T, ? super Throwable> action);public CompletionStage<T> whenCompleteAsync(BiConsumer<? super T, ? super Throwable> action,Executor executor);

接收执行完成的结果,返回值或异常,不会影响最终执行结果。
与exceptionally进行比较,测试代码:

@Testpublic void whenComplete() {    String result = CompletableFuture.supplyAsync(() -> {        try {            Thread.sleep(2000);        } catch (InterruptedException e) {            e.printStackTrace();        }        return "s1";    }).whenComplete((s, t) -> {        System.out.println(s);        System.out.println(t);        int i = 1/0;    }).exceptionally(e -> {        System.out.println(e.getMessage());        return "hello world";    }).join();    System.out.println(result);}

输出测试结果:

s1nulljava.lang.ArithmeticException: / by zerohello world

注意whenComplete中参数的空指针问题。
另一种情况:

@Testpublic void whenComplete() {    String result = CompletableFuture.supplyAsync(() -> {        try {            Thread.sleep(2000);        } catch (InterruptedException e) {            e.printStackTrace();        }int i = 1/0;        return "s1";    }).whenComplete((s, t) -> {        System.out.println(s);        System.out.println(t);    }).exceptionally(e -> {        System.out.println(e.getMessage());        return "hello world";    }).join();    System.out.println(result);}

执行结果:

nulljava.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zerojava.lang.ArithmeticException: / by zerohello world

可见,whenComplete只是将前一个的执行结果,无论是异常还是正常结果,传递到下面去。

12.handle,结果处理

相关方法:

public <U> CompletionStage<U> handle(BiFunction<? super T, Throwable, ? extends U> fn);public <U> CompletionStage<U> handleAsync(BiFunction<? super T, Throwable, ? extends U> fn);public <U> CompletionStage<U> handleAsync(BiFunction<? super T, Throwable, ? extends U> fn,Executor executor);

所谓结果,包含两种,一种是喜闻乐见的正常结果,另一种是“喜闻乐见”的异常结果。
发生异常测试:

@Testpublic void handle() {    String result = CompletableFuture.supplyAsync(() -> {        try {            Thread.sleep(3000);        } catch (InterruptedException e) {            e.printStackTrace();        }        //出现异常        int i =1/0;        return "s1";    }).handle((s, t) -> {        if (t != null) {            return "s2";        }        return s;    }).join();    System.out.println(result);}

执行结果:
s2
无异常测试:

@Testpublic void handle() {    String result = CompletableFuture.supplyAsync(() -> {        try {            Thread.sleep(2000);        } catch (InterruptedException e) {            e.printStackTrace();        }        return "s1";    }).handle((s, t) -> {        if (t != null) {            return "s2";        }        return s;    }).join();    System.out.println(result);}

结果:

s1

对比whenComplete,它功能更加强大,增加了可以影响最终结果的功能。

以上只是简单的单个或两个的结合方法的测试,在实际的业务中所需处理的逻辑只会更加复杂。CompletableFuture类的泛型和流式调用设计,在复杂业务的任务流程调用上有一定的便利。

参考:
简书:数齐—CompletableFuture 详解

原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 打了肉毒素淤青怎么办 新生儿一吃母乳就睡觉怎么办 10岁半宝宝缺钾怎么办 宝宝吃不到乳晕怎么办 喂母乳乳头破了怎么办 宝宝吸了乳头痛怎么办 喂母乳奶头很疼怎么办 孩子吸的乳头痛怎么办 新生儿一直睡觉不吸吮怎么办 新生儿光睡觉不吃奶怎么办 婴儿不吸母亲的奶怎么办 肾里有结石一直不掉下来怎么办 宝宝拍不出来嗝怎么办 未满月宝宝溢奶怎么办 吃母乳老是吐奶怎么办 宝宝吃母乳吐奶怎么办 新生儿三天没大便了怎么办 奶水多宝宝呛奶怎么办 婴儿的含乳姿势不正确怎么办 一个月竖着抱了怎么办 乳头被婴儿吸痛怎么办 婴儿从鼻孔吐奶怎么办 宝宝习惯含着乳头睡觉怎么办? 宝宝习惯奶瓶不吃乳头怎么办 婴儿从鼻子吐奶怎么办 宝宝从鼻孔出奶怎么办 一个月婴儿溢奶怎么办 刚出生婴儿溢奶怎么办 宝宝含不住乳晕怎么办 宝宝含不了乳晕怎么办 宝宝溢奶一直哭怎么办 宝宝倒奶很多次怎么办 宝宝吃了润肤乳怎么办 奶瓶很难吸出奶怎么办 2个月宝宝奶睡怎么办 孕四个月轻度缺碘怎么办 宝宝吃了奶粉呕吐怎么办 宝宝吃奶只含乳头怎么办 母乳流的太快怎么办 两个月小孩乳头混淆了怎么办 喂奶期间乳房有肿块怎么办