Java CompletableFuture 之线程编排

一,背景

Future是Java 5添加的类,用来描述一个异步计算的结果。你可以使用isDone方法检查计算是否完成,或者使用get阻塞住调用线程,直到计算完成返回结果,你也可以使用cancel方法停止任务的执行。

public class BasicFuture {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService es = Executors.newFixedThreadPool(10);
        Future f = es.submit(() ->{
                // 长时间的异步计算
                // ……
                // 然后返回结果
                return 100;
            });
//        while(!f.isDone());
        f.get();
    }
}

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

Google guava提供了通用的扩展Future:ListenableFuture、SettableFuture 以及辅助类Futures等,方便异步编程。

final String name = ...;
inFlight.add(name);
ListenableFuture future = service.query(name);
future.addListener(new Runnable() {
  public void run() {
    processedCount.incrementAndGet();
    inFlight.remove(name);
    lastProcessed.set(name);
    logger.info("Done with {0}", name);
  }
}, executor);

作为正统的Java类库,是不是应该做点什么,加强一下自身库的功能呢?

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

下面我们就看一看它的功能吧。

CompletableFuture类实现了CompletionStage和Future接口,所以你还是可以像以前一样通过阻塞或者轮询的方式获得结果,尽管这种方式不推荐使用。

public T 	get()
public T 	get(long timeout, TimeUnit unit)
public T 	getNow(T valueIfAbsent)
public T 	join()

getNow有点特殊,如果结果已经计算完则返回结果或者抛出异常,否则返回给定的valueIfAbsent值。
join返回计算的结果或者抛出一个unchecked异常(CompletionException),它和get对抛出的异常的处理有些细微的区别,你可以运行下面的代码进行比较:

CompletableFuture future = CompletableFuture.supplyAsync(() -> {
    int i = 1/0;
    return 100;
});
//future.join();
future.get();

二,创建CompletableFuture对象

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

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

public static CompletableFuture 	runAsync(Runnable runnable)
public static CompletableFuture 	runAsync(Runnable runnable, Executor executor)
public static  CompletableFuture 	supplyAsync(Supplier supplier)
public static  CompletableFuture 	supplyAsync(Supplier supplier, Executor executor)

Async结尾并且没有指定Executor的方法会使用ForkJoinPool.commonPool()作为它的线程池执行异步代码。

runAsync方法也好理解,它以Runnable函数式接口类型为参数,所以CompletableFuture的计算结果为空。

supplyAsync方法以Supplier函数式接口类型为参数,CompletableFuture的计算结果类型为U

因为方法的参数类型都是函数式接口,所以可以使用lambda表达式实现异步任务,比如:

CompletableFuture future = CompletableFuture.supplyAsync(() -> {
    //长时间的计算任务
    return "·00";
});

三,计算结果完成时的处理

CompletableFuture的计算结果完成,或者抛出异常的时候,我们可以执行特定的Action。主要是下面的方法:

public CompletableFuture 	whenComplete(BiConsumer action)
public CompletableFuture 	whenCompleteAsync(BiConsumer action)
public CompletableFuture 	whenCompleteAsync(BiConsumer action, Executor executor)
public CompletableFuture     exceptionally(Function fn)

可以看到Action的类型是BiConsumer,它可以处理正常的计算结果,或者异常情况。
方法不以Async结尾,意味着Action使用相同的线程执行,而Async可能会使用其它的线程去执行(如果使用相同的线程池,也可能会被同一个线程选中执行)。

注意这几个方法都会返回CompletableFuture,当Action执行完毕后它的结果返回原始的CompletableFuture的计算结果或者返回异常。

public class Main {
    private static Random rand = new Random();
    private static long t = System.currentTimeMillis();
    static int getMoreData() {
        System.out.println("begin to start compute");
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("end to start compute. passed " + (System.currentTimeMillis() - t)/1000 + " seconds");
        return rand.nextInt(1000);
    }
    public static void main(String[] args) throws Exception {
        CompletableFuture future = CompletableFuture.supplyAsync(Main::getMoreData);
        Future f = future.whenComplete((v, e) -> {
            System.out.println(v);
            System.out.println(e);
        });
        System.out.println(f.get());
        System.in.read();
    }
}

exceptionally方法返回一个新的CompletableFuture,当原始的CompletableFuture抛出异常的时候,就会触发这个CompletableFuture的计算,调用function计算值,否则如果原始的CompletableFuture正常计算完后,这个新的CompletableFuture也计算完成,它的值和原始的CompletableFuture的计算的值相同。也就是这个exceptionally方法用来处理异常的情况。

下面一组方法虽然也返回CompletableFuture对象,但是对象的值和原来的CompletableFuture计算的值不同。当原先的CompletableFuture的值计算完成或者抛出异常的时候,会触发这个CompletableFuture对象的计算,结果由BiFunction参数计算而得。因此这组方法兼有whenComplete和转换的两个功能。

public  CompletableFuture 	handle(BiFunction fn)
public  CompletableFuture 	handleAsync(BiFunction fn)
public  CompletableFuture 	handleAsync(BiFunction fn, Executor executor)

同样,不以Async结尾的方法由原来的线程计算,以Async结尾的方法由默认的线程池ForkJoinPool.commonPool()或者指定的线程池executor运行。

四,转换

CompletableFuture可以作为monad(单子)和functor。由于回调风格的实现,我们不必因为等待一个计算完成而阻塞着调用线程,而是告诉CompletableFuture当计算完成的时候请执行某个function。而且我们还可以将这些操作串联起来,或者将CompletableFuture组合起来。

public  CompletableFuture 	thenApply(Function fn)
public  CompletableFuture 	thenApplyAsync(Function fn)
public  CompletableFuture 	thenApplyAsync(Function fn, Executor executor)

这一组函数的功能是当原来的CompletableFuture计算完后,将结果传递给函数fn,将fn的结果作为新的CompletableFuture计算结果。因此它的功能相当于将CompletableFuture转换成CompletableFuture

这三个函数的区别和上面介绍的一样,不以Async结尾的方法由原来的线程计算,以Async结尾的方法由默认的线程池ForkJoinPool.commonPool()或者指定的线程池executor运行。Java的CompletableFuture类总是遵循这样的原则,下面就不一一赘述了。

使用例子如下:

CompletableFuture future = CompletableFuture.supplyAsync(() -> {
    return 100;
});
CompletableFuture f =  future.thenApplyAsync(i -> i * 10).thenApply(i -> i.toString());
System.out.println(f.get()); //"1000"

需要注意的是,这些转换并不是马上执行的,也不会阻塞,而是在前一个stage完成后继续执行。

它们与handle方法的区别在于handle方法会处理正常计算值和异常,因此它可以屏蔽异常,避免异常继续抛出。而thenApply方法只是用来处理正常值,因此一旦有异常就会抛出。

五,纯消费(执行Action)

上面的方法是当计算完成的时候,会生成新的计算结果(thenApplyhandle),或者返回同样的计算结果whenCompleteCompletableFuture还提供了一种处理结果的方法,只对结果执行Action,而不返回新的计算值,因此计算值为Void:

public CompletableFuture 	thenAccept(Consumer action)
public CompletableFuture 	thenAcceptAsync(Consumer action)
public CompletableFuture 	thenAcceptAsync(Consumer action, Executor executor)

看它的参数类型也就明白了,它们是函数式接口Consumer,这个接口只有输入,没有返回值。

CompletableFuture future = CompletableFuture.supplyAsync(() -> {
    return 100;
});
CompletableFuture f =  future.thenAccept(System.out::println);
System.out.println(f.get());

thenAcceptBoth以及相关方法提供了类似的功能,当两个CompletionStage都正常完成计算的时候,就会执行提供的action,它用来组合另外一个异步的结果。
runAfterBoth是当两个CompletionStage都正常完成计算的时候,执行一个Runnable,这个Runnable并不使用计算的结果。

public  CompletableFuture 	thenAcceptBoth(CompletionStage other, BiConsumer action)
public  CompletableFuture 	thenAcceptBothAsync(CompletionStage other, BiConsumer action)
public  CompletableFuture 	thenAcceptBothAsync(CompletionStage other, BiConsumer action, Executor executor)
public     CompletableFuture 	runAfterBoth(CompletionStage other,  Runnable action)

例子如下:

CompletableFuture future = CompletableFuture.supplyAsync(() -> {
    return 100;
});
CompletableFuture f =  future.thenAcceptBoth(CompletableFuture.completedFuture(10), (x, y) -> System.out.println(x * y));
System.out.println(f.get());

更彻底地,下面一组方法当计算完成的时候会执行一个Runnable,与thenAccept不同,Runnable并不使用CompletableFuture计算的结果。

public CompletableFuture 	thenRun(Runnable action)
public CompletableFuture 	thenRunAsync(Runnable action)
public CompletableFuture 	thenRunAsync(Runnable action, Executor executor)

因此先前的CompletableFuture计算的结果被忽略了,这个方法返回CompletableFuture类型的对象。

CompletableFuture future = CompletableFuture.supplyAsync(() -> {
    return 100;
});
CompletableFuture f =  future.thenRun(() -> System.out.println("finished"));
System.out.println(f.get());

因此,你可以根据方法的参数的类型来加速你的记忆。Runnable类型的参数会忽略计算的结果,Consumer是纯消费计算结果,BiConsumer会组合另外一个CompletionStage纯消费,Function会对计算结果做转换,BiFunction会组合另外一个CompletionStage的计算结果做转换。

五,组合

public  CompletableFuture 	thenCompose(Function> fn)
public  CompletableFuture 	thenComposeAsync(Function> fn)
public  CompletableFuture 	thenComposeAsync(Function> fn, Executor executor)

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

A +--> B +---> C

记住,thenCompose返回的对象并不一是函数fn返回的对象,如果原来的CompletableFuture还没有计算出来,它就会生成一个新的组合后的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的结果。它的功能类似:

A +
  |
  +------> C
  +------^
B +

两个CompletionStage是并行执行的,它们之间并没有先后依赖顺序,other并不会等待先前的CompletableFuture执行完毕后再执行。

public  CompletableFuture 	thenCombine(CompletionStage other, BiFunction fn)
public  CompletableFuture 	thenCombineAsync(CompletionStage other, BiFunction fn)
public  CompletableFuture 	thenCombineAsync(CompletionStage other, BiFunction 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

六,Either

thenAcceptBothrunAfterBoth是当两个CompletableFuture都计算完成,而我们下面要了解的方法是当任意一个CompletableFuture计算完成的时候就会执行。

public CompletableFuture 	acceptEither(CompletionStage other, Consumer action)
public CompletableFuture 	acceptEitherAsync(CompletionStage other, Consumer action)
public CompletableFuture 	acceptEitherAsync(CompletionStage other, Consumer action, Executor executor)
public  CompletableFuture 	applyToEither(CompletionStage other, Function fn)
public  CompletableFuture 	applyToEitherAsync(CompletionStage other, Function fn)
public  CompletableFuture 	applyToEitherAsync(CompletionStage other, Function fn, Executor executor)

acceptEither方法是当任意一个CompletionStage完成的时候,action这个消费者就会被执行。这个方法返回CompletableFuture

applyToEither方法是当任意一个CompletionStage完成的时候,fn会被执行,它的返回值会当作新的CompletableFuture的计算结果。

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

Random rand = new Random();
CompletableFuture future = 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 200;
});
CompletableFuture f =  future.applyToEither(future2,i -> i.toString());

七,辅助方法 allOf 和 anyOf

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

public static CompletableFuture 	    allOf(CompletableFuture... cfs)
public static CompletableFuture 	anyOf(CompletableFuture... cfs)
 
  

allOf方法是当所有的CompletableFuture都执行完后执行计算。
anyOf方法是当任意一个CompletableFuture执行完后就会执行计算,计算的结果相同。

下面的代码运行结果有时是100,有时是"abc"。但是anyOfapplyToEither不同。anyOf接受任意多的CompletableFuture但是applyToEither只是判断两个CompletableFuture,anyOf返回值的计算结果是参数中其中一个CompletableFuture的计算结果,applyToEither返回值的计算结果却是要经过fn处理的。当然还有静态方法的区别,线程池的选择等。

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()); 
  

我想通过上面的介绍,应该把CompletableFuture的方法和功能介绍完了(cancelisCompletedExceptionally()isDone()以及继承于Object的方法无需介绍了, toCompletableFuture()返回CompletableFuture本身),希望你能全面了解CompletableFuture强大的功能,并将它应用到Java的异步编程中。

如果你用过Guava的Future类,你就会知道它的Futures辅助类提供了很多便利方法,用来处理多个Future,而不像Java的CompletableFuture,只提供了allOfanyOf两个方法。 比如有这样一个需求,将多个CompletableFuture组合成一个CompletableFuture,这个组合后的CompletableFuture的计算结果是个List,它包含前面所有的CompletableFuture的计算结果,guava的Futures.allAsList可以实现这样的功能,但是对于java CompletableFuture,我们需要一些辅助方法:

  public static  CompletableFuture> sequence(List> futures) {
       CompletableFuture allDoneFuture = CompletableFuture.allOf(futures.toArray(new CompletableFuture[futures.size()]));
       return allDoneFuture.thenApply(v -> futures.stream().map(CompletableFuture::join).collect(Collectors.toList()));
   }
public static  CompletableFuture> sequence(Stream> futures) {
       List> futureList = futures.filter(f -> f != null).collect(Collectors.toList());
       return sequence(futureList);
   }

或者Java Future转CompletableFuture:

public static  CompletableFuture toCompletable(Future future, Executor executor) {
    return CompletableFuture.supplyAsync(() -> {
        try {
            return future.get();
        } catch (InterruptedException | ExecutionException e) {
            throw new RuntimeException(e);
        }
    }, executor);
}

github有多个项目可以实现Java CompletableFuture与其它Future (如Guava ListenableFuture)之间的转换,如spotify/futures-extra、future-converter、scala/scala-java8-compat 等。

八,使用示例

CompletionStage是一个接口,从命名上看得知是一个完成的阶段,它里面的方法也标明是在某个运行阶段得到了结果之后要做的事情。

1,进行变换

public  CompletionStage thenApply(Function fn);
public  CompletionStage thenApplyAsync(Function fn);
public  CompletionStage thenApplyAsync(Function fn,Executor executor);

首先说明一下已Async结尾的方法都是可以异步执行的,如果指定了线程池,会在指定的线程池中执行,如果没有指定,默认会在ForkJoinPool.commonPool()中执行,下文中将会有好多类似的,都不详细解释了。关键的入参只有一个Function,它是函数式接口,所以使用Lambda表示起来会更加优雅。它的入参是上一个阶段计算后的结果,返回值是经过转化后结果。
例如:

    @Test
    public void thenApply() {
        String result = CompletableFuture.supplyAsync(() -> "hello").thenApply(s -> s + " world").join();
        System.out.println(result);
    }

结果为:

hello world

2,进行消耗

public CompletionStage thenAccept(Consumer action);
public CompletionStage thenAcceptAsync(Consumer action);
public CompletionStage thenAcceptAsync(Consumer action,Executor executor);

thenAccept是针对结果进行消耗,因为他的入参是Consumer,有入参无返回值。
例如:

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

结果为:

hello world

3,对上一步的计算结果不关心,执行下一个操作

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

thenRun它的入参是一个Runnable的实例,表示当得到上一步的结果时的操作。
例如:

    @Test
    public void thenRun(){
        CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "hello";
        }).thenRun(() -> System.out.println("hello world"));
        while (true){}
    }

结果为:

hello world

4.结合两个CompletionStage的结果,进行转化后返回

public  CompletionStage thenCombine(CompletionStage other,BiFunction fn);
public  CompletionStage thenCombineAsync(CompletionStage other,BiFunction fn);
public  CompletionStage thenCombineAsync(CompletionStage other,BiFunction fn,Executor executor);

它需要原来的处理返回值,并且other代表的CompletionStage也要返回值之后,利用这两个返回值,进行转换后返回指定类型的值。
例如:

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

结果为:

hello world

5,结合两个CompletionStage的结果,进行消耗

public  CompletionStage thenAcceptBoth(CompletionStage other,BiConsumer action);
public  CompletionStage thenAcceptBothAsync(CompletionStage other,BiConsumer action);
public  CompletionStage thenAcceptBothAsync(CompletionStage other,BiConsumer action,     Executor executor);

它需要原来的处理返回值,并且other代表的CompletionStage也要返回值之后,利用这两个返回值,进行消耗。
例如:

    @Test
    public void thenAcceptBoth() {
        CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "hello";
        }).thenAcceptBoth(CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "world";
        }), (s1, s2) -> System.out.println(s1 + " " + s2));
        while (true){}
    }

结果为:

hello world

6,在两个CompletionStage都运行完执行。

public CompletionStage runAfterBoth(CompletionStage other,Runnable action);
public CompletionStage runAfterBothAsync(CompletionStage other,Runnable action);
public CompletionStage runAfterBothAsync(CompletionStage other,Runnable action,Executor executor);

不关心这两个CompletionStage的结果,只关心这两个CompletionStage执行完毕,之后在进行操作(Runnable)。
例如:

    @Test
    public void runAfterBoth(){
        CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "s1";
        }).runAfterBothAsync(CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "s2";
        }), () -> System.out.println("hello world"));
        while (true){}
    }

结果为

hello world

7,两个CompletionStage,谁计算的快,我就用那个CompletionStage的结果进行下一步的转化操作。

public  CompletionStage applyToEither(CompletionStage other,Function fn);
public  CompletionStage applyToEitherAsync(CompletionStage other,Function fn);
public  CompletionStage applyToEitherAsync(CompletionStage other,Function fn,Executor executor);

我们现实开发场景中,总会碰到有两种渠道完成同一个事情,所以就可以调用这个方法,找一个最快的结果进行处理。
例如:

    @Test
    public void applyToEither() {
        String result = CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "s1";
        }).applyToEither(CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "hello world";
        }), s -> s).join();
        System.out.println(result);
    }

结果为:

hello world

8,两个CompletionStage,谁计算的快,我就用那个CompletionStage的结果进行下一步的消耗操作。

public CompletionStage acceptEither(CompletionStage other,Consumer action);
public CompletionStage acceptEitherAsync(CompletionStage other,Consumer action);
public CompletionStage acceptEitherAsync(CompletionStage other,Consumer action,Executor executor);

例如:

    @Test
    public void acceptEither() {
        CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "s1";
        }).acceptEither(CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "hello world";
        }), System.out::println);
        while (true){}
    }

结果为:

hello world

9,两个CompletionStage,任何一个完成了都会执行下一步的操作(Runnable)。

public CompletionStage runAfterEither(CompletionStage other,Runnable action);
public CompletionStage runAfterEitherAsync(CompletionStage other,Runnable action);
public CompletionStage runAfterEitherAsync(CompletionStage other,Runnable action,Executor executor);

例如:

    @Test
    public void runAfterEither() {
        CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "s1";
        }).runAfterEither(CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "s2";
        }), () -> System.out.println("hello world"));
        while (true) {
        }
    }

结果为:

hello world

10,当运行时出现了异常,可以通过exceptionally进行补偿。

public CompletionStage exceptionally(Function fn);

例如:

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

结果为:

java.lang.RuntimeException: 测试一下异常情况
hello world

11,当运行完成时,对结果的记录。这里的完成时有两种情况,一种是正常执行,返回值。另外一种是遇到异常抛出造成程序的中断。这里为什么要说成记录,因为这几个方法都会返回CompletableFuture,当Action执行完毕后它的结果返回原始的CompletableFuture的计算结果或者返回异常。所以不会对结果产生任何的作用。

public CompletionStage whenComplete(BiConsumer action);
public CompletionStage whenCompleteAsync(BiConsumer action);
public CompletionStage whenCompleteAsync(BiConsumer action,Executor executor);

例如:

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

结果为:

null
java.lang.RuntimeException: 测试一下异常情况
java.lang.RuntimeException: 测试一下异常情况
hello world

这里也可以看出,如果使用了exceptionally,就会对最终的结果产生影响,它没有口子返回如果没有异常时的正确的值,这也就引出下面我们要介绍的handle。

12,运行完成时,对结果的处理。这里的完成时有两种情况,一种是正常执行,返回值。另外一种是遇到异常抛出造成程序的中断。

public  CompletionStage handle(BiFunction fn);
public  CompletionStage handleAsync(BiFunction fn);
public  CompletionStage handleAsync(BiFunction fn,Executor executor);

例如:
出现异常时

    @Test
    public void handle() {
        String result = CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //出现异常
            if (1 == 1) {
                throw new RuntimeException("测试一下异常情况");
            }
            return "s1";
        }).handle((s, t) -> {
            if (t != null) {
                return "hello world";
            }
            return s;
        }).join();
        System.out.println(result);
    }

结果为:

hello world

未出现异常时

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

结果为:

s1

上面就是CompletionStage接口中方法的使用实例,CompletableFuture同样也同样实现了Future,所以也同样可以使用get进行阻塞获取值,总的来说,CompletableFuture使用起来还是比较爽的,看起来也比较优雅一点。

 

你可能感兴趣的:(线程)