Java8 CompletableFuture详解以及API part1

API主要参考:CompletableFuture 使用详解

引入原因

很难表述Future结果之间的依赖性,无法实现类似于如下的需求:

  1. 将两个异步计算合并为一个——这两个异步计算之间相互独立,同时第二个又依赖于第一个的结果。
  2. 等待Future集合中的所有任务都完成。
  3. 仅等待Future集合中最快结束的任务完成(有可能因为它们试图通过不同的方式计算同一个值),并返回它的结果。
  4. 通过编程方式完成一个Future任务的执行(即以手工设定异步操作结果的方式)。
  5. 应对Future的完成事件(即当Future的完成事件发生时会收到通知,并能使用Future计算的结果进行下一步的操作,不只是简单地阻塞等待操作的结果)。

Stream和CompletableFuture的设计都遵循了类似的模式:它们都使用了Lambda表达式以及流水线的思想。从这个角度,可以说CompletableFuture和Future的关系就跟Stream和Collection的关系一样。

并行流还是CompletableFutures

我们对使用这些API的建议如下。
❑ 如果你进行的是计算密集型的操作,并且没有I/O,那么推荐使用Stream接口,因为实现简单,同时效率也可能是最高的(如果所有的线程都是计算密集型的,那就没有必要创建比处理器核数更多的线程)。
❑ 反之,如果你并行的工作单元还涉及等待I/O的操作(包括网络连接等待),那么使用CompletableFuture灵活性更好,你可以像前文讨论的那样,依据等待/计算,或者W/C的比率设定需要使用的线程数。这种情况不使用并行流的另一个原因是,处理流的流水线中如果发生I/O等待,流的延迟特性会让我们很难判断到底什么时候触发了等待。

Java8实战写法实例

public Future<Double> getPriceAsync(String product) {
    
    
	CompletableFuture<Double> futurePrice = new CompletableFuture<>();
	new Thread( () -> {
    
    
		try {
    
    
			double price = calculatePrice(product);
			futurePrice.complete(price);
		} catch (Exception ex) {
    
    
			futurePrice.completeExceptionally(ex);
		}
	}).start();
	return futurePrice;
}
// 调用工厂方法的版本
public List<String> findPrices(String product) {
    
    
	List<CompletableFuture<String>> priceFutures =
	shops.stream()
	.map(shop -> CompletableFuture.supplyAsync(
		() -> shop.getName() + " price is " +
		shop.getPrice(product)))
	.collect(Collectors.toList());
	return priceFutures.stream()
		.map(CompletableFuture::join)
		.collect(toList());
}
// 详情参见11.4节
public List<String> findPrices(String product) {
    
    
	List<CompletableFuture<String>> priceFutures =
	shops.stream()
	.map(shop -> CompletableFuture.supplyAsync(
		() -> shop.getPrice(product), executor))
	.map(future -> future.thenApply(Quote::parse))
	.map(future -> future.thenCompose(quote ->
		CompletableFuture.supplyAsync(
		() -> Discount.applyDiscount(quote), executor)))
	.collect(toList());
	return priceFutures.stream()
		.map(CompletableFuture::join)
		.collect(toList());
}

// 调用处
Shop shop = new Shop("BestShop");
long start = System.nanoTime();
Future<Double> futurePrice = shop.getPriceAsync("my favorite product");
long invocationTime = ((System.nanoTime() - start) / 1_000_000);
System.out.println("Invocation returned after " + invocationTime
+ " msecs");
// 执行更多任务,比如查询其他商店
doSomethingElse();
// 在计算商品价格的同时
try {
    
    
double price = futurePrice.get();
System.out.printf("Price is %.2f%n", price);
} catch (Exception e) {
    
    
throw new RuntimeException(e);
}
long retrievalTime = ((System.nanoTime() - start) / 1_000_000);
System.out.println("Price returned after " + retrievalTime + " msecs");

runAsync 和 supplyAsync方法

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)

没有指定Executor的方法会使用ForkJoinPool.commonPool() 作为它的线程池执行异步代码。这个线程池默认创建的线程数是 CPU 的核数(也可以通过 JVM option:-Djava.util.concurrent.ForkJoinPool.common.parallelism 来设置 ForkJoinPool 线程池的线程数)。如果指定线程池,则使用指定的线程池运行。以下所有的方法都类同。
• runAsync方法不支持返回值。
• supplyAsync可以支持返回值。

使用定制的执行器的例子,参见《Java8实战》11.3.4节

示例

//无返回值
public static void runAsync() throws Exception {
    
    
    CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
    
    
        try {
    
    
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
    
    
        }
        System.out.println("run end ...");
    });
    
    future.get();
}
//有返回值
public static void supplyAsync() throws Exception {
    
             
    CompletableFuture<Long> future = CompletableFuture.supplyAsync(() -> {
    
    
        try {
    
    
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
    
    
        }
        System.out.println("run end ...");
        return System.currentTimeMillis();
    });
long time = future.get();
    System.out.println("time = "+time);
}

三种关系

任务是有时序关系的,比如有串行关系、并行关系、汇聚关系等。汇聚关系是指某个动作需要等待两种并行关系都完成后才能进行。之后的API基本上与这三种关系有关。
串行关系
串行API:
CompletionStage thenApply(fn);
CompletionStage thenApplyAsync(fn);
CompletionStage thenAccept(consumer);
CompletionStage thenAcceptAsync(consumer);
CompletionStage thenRun(action);
CompletionStage thenRunAsync(action);
CompletionStage thenCompose(fn);
CompletionStage thenComposeAsync(fn);

并行关系
汇聚关系
汇聚关系AND API:
CompletionStage thenCombine(other, fn);
CompletionStage thenCombineAsync(other, fn);
CompletionStage thenAcceptBoth(other, consumer);
CompletionStage thenAcceptBothAsync(other, consumer);
CompletionStage runAfterBoth(other, action);
CompletionStage runAfterBothAsync(other, action);

汇聚关系OR API:
CompletionStage applyToEither(other, fn);
CompletionStage applyToEitherAsync(other, fn);
CompletionStage acceptEither(other, consumer);
CompletionStage acceptEitherAsync(other, consumer);
CompletionStage runAfterEither(other, action);
CompletionStage runAfterEitherAsync(other, action);

计算结果完成时的回调方法

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

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)

可以看到Action的类型是BiConsumer<? super T,? super Throwable>它可以处理正常的计算结果,或者异常情况。
whenComplete 和 whenCompleteAsync 的区别:

  1. whenComplete:是执行当前任务的线程执行继续执行 whenComplete 的任务。
  2. whenCompleteAsync:是执行把 whenCompleteAsync 这个任务继续提交给线程池来进行执行。
    示例:
public static void whenComplete() throws Exception {
    
    
    CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
    
    
        try {
    
    
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
    
    
        }
        if(new Random().nextInt()%2>=0) {
    
    
            int i = 12/0;
        }
        System.out.println("run end ...");
    });
    
    future.whenComplete(new BiConsumer<Void, Throwable>() {
    
    
        @Override
        public void accept(Void t, Throwable action) {
    
    
            System.out.println("执行完成!");
        }
        
    });
    future.exceptionally(new Function<Throwable, Void>() {
    
    
        @Override
        public Void apply(Throwable t) {
    
    
            System.out.println("执行失败!"+t.getMessage());
            return null;
        }
    });
    
    TimeUnit.SECONDS.sleep(2);
}

thenApply 方法

当一个线程依赖另一个线程时,可以使用 thenApply 方法来把这两个线程串行化。

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

Function<? super T,? extends U>
T:上一个任务返回结果的类型
U:当前任务的返回值类型
示例:

private static void thenApply() throws Exception {
    
    
    CompletableFuture<Long> future = CompletableFuture.supplyAsync(new Supplier<Long>() {
    
    
        @Override
        public Long get() {
    
    
            long result = new Random().nextInt(100);
            System.out.println("result1="+result);
            return result;
        }
    }).thenApply(new Function<Long, Long>() {
    
    
        @Override
        public Long apply(Long t) {
    
    
            long result = t*5;
            System.out.println("result2="+result);
            return result;
        }
    });
    
    long result = future.get();
    System.out.println(result);
}

第二个任务依赖第一个任务的结果。

handle 方法

handle 是执行任务完成时对结果的处理。
handle 方法和 thenApply 方法处理方式基本一样。不同的是 handle 是在任务完成后再执行,还可以处理异常的任务。thenApply 只可以执行正常的任务,任务出现异常则不执行 thenApply 方法。

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

示例

public static void handle() throws Exception{
    
    
    CompletableFuture<Integer> future = CompletableFuture.supplyAsync(new Supplier<Integer>() {
    
    
@Override
        public Integer get() {
    
    
            int i= 10/0;
            return new Random().nextInt(10);
        }
    }).handle(new BiFunction<Integer, Throwable, Integer>() {
    
    
        @Override
        public Integer apply(Integer param, Throwable throwable) {
    
    
            int result = -1;
            if(throwable==null){
    
    
                result = param * 2;
            }else{
    
    
                System.out.println(throwable.getMessage());
            }
            return result;
        }
     });
    System.out.println(future.get());
}

从示例中可以看出,在 handle 中可以根据任务是否有异常来进行做相应的后续处理操作。而 thenApply 方法,如果上个任务出现错误,则不会执行 thenApply 方法。

thenAccept 消费处理结果

接收任务的处理结果,并消费处理,无返回结果。

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

示例

public static void thenAccept() throws Exception{
    
    
    CompletableFuture<Void> future = CompletableFuture.supplyAsync(new Supplier<Integer>() {
    
    
        @Override
        public Integer get() {
    
    
            return new Random().nextInt(10);
        }
    }).thenAccept(integer -> {
    
    
        System.out.println(integer);
    });
    future.get();
}

从示例代码中可以看出,该方法只是消费执行完成的任务,并可以根据上面的任务返回的结果进行处理。并没有后续的输错操作。

thenRun 方法

跟 thenAccept 方法不一样的是,不关心任务的处理结果。只要上面的任务执行完成,就开始执行 thenAccept 。

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

示例

public static void thenRun() throws Exception{
    
    
    CompletableFuture<Void> future = CompletableFuture.supplyAsync(new Supplier<Integer>() {
    
    
        @Override
        public Integer get() {
    
    
            return new Random().nextInt(10);
        }
    }).thenRun(() -> {
    
    
        System.out.println("thenRun ...");
    });
    future.get();
}

该方法同 thenAccept 方法类似。不同的是上个任务处理完成后,并不会把计算的结果传给 thenRun 方法。只是处理玩任务后,执行 thenAccept 的后续操作。

猜你喜欢

转载自blog.csdn.net/weixin_38370441/article/details/112324593