CompletableFuture异步编程

1.同步API与异步API

  • 同步API其实只是对传统方法调用的另一种称呼:你调用了某个方法,调用方在被调用方运行的过程中会等待,被调用方运行结束返回,调用方取得被调用方的返回值并继续运行。即使调用方和被调用方在同的线程中运行,调用方还是需要等待被调用方结束运行,这就是阻塞式调用这个名词的由来。
  • 与此相反,异步API会直接返回,或者至少在被调用方计算完成之前,将它剩余的计算任务交给另一个线程去做,该线程和调用方是异步的——这就是非阻塞式调用的由来。执行剩余计算任务的线程会将它的计算结果返回给调用方。返回的方式要么是通过回调函数,要么是由调用方再次执行一个“等待,直到计算完成”的方法调用。这种方式的计算在I/O系统程序设计中非常常见:你发起了一次磁盘访问,这次访问和你的其他计算操作是异步的,你完成其他的任务时,磁盘块的数据可能还没载入到内存,你只需要等待数据的载入完成。

2.Future 接口的局限性

  • 虽然 Future 以及相关使用方法提供了异步执行任务的能力,但是对于结果的获取却是很不方便,只能通过阻塞或者轮询的方式得到任务的结果。
    阻塞的方式显然和我们的异步编程的初衷相违背,轮询的方式又会耗费无谓的 CPU 资源,而且也不能及时地得到计算结果。
  • 它很难直接表述多个Future 结果之间的依赖性。实际开发中,我们经常需要达成以下目的:
    (1) 将多个异步计算的结果合并成一个
    (2) 等待Future集合中的所有任务都完成
    (3) Future完成事件(即任务完成以后触发执行动作)
    (4)…

3.CompletionStage

CompletionStage代表异步计算过程中的某一个阶段,一个阶段完成以后可能会触发另外一个阶段。

一个阶段的计算执行可以是一个Function,Consumer或者Runnable。比如:stage.thenApply(x -> square(x)).thenAccept(x -> System.out.print(x)).thenRun(() -> System.out.println())

一个阶段的执行可能是被单个阶段的完成触发,也可能是由多个阶段一起触发

4.CompletableFuture

在Java8中,CompletableFuture提供了非常强大的Future的扩展功能,可以帮助我们简化异步编程的复杂性,并且提供了函数式编程的能力,可以通过回调的方式处理计算结果,也提供了转换和组合 CompletableFuture 的方法。

它可能代表一个明确完成的Future,也有可能代表一个完成阶段( CompletionStage ),它支持在计算完成以后触发一些函数或执行某些动作。

它实现了Future和CompletionStage接口

4.1 supplyAsync

public class CompletableFutureInAction {
    
    
    public static void main(String[] args){
    
    
        CompletableFuture.supplyAsync(CompletableFutureInAction::get)
                .whenComplete((v, t) -> {
    
    
                    Optional.ofN(v).ifPresent(System.out::println);
                    Optional.of(t).ifPresent(x -> x.printStackTrace());
                });
        System.out.println("====i am no ---block----");
    }
	
	static double get() {
    
    
        try {
    
    
            Thread.sleep(RANDOM.nextInt(10000));
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        double result = RANDOM.nextDouble();
        System.out.println(result);
        return result;
    }
}

//执行结果发现:
  (1)主线程会立即输出====i am no ---block----,而不是先等待异步任务执行完再输出
  (2)异步任务会未执行,整个程序都退出了(这是因为supplyAsync工厂方法,使用的线程池里的线程默认是守护线程)
  

public class CompletableFutureInAction {
    
    

    public static void main(String[] args)
            throws InterruptedException {
    
    
        AtomicBoolean finished = new AtomicBoolean(false);
        ExecutorService executor = Executors.newFixedThreadPool(2, r -> {
    
    
            Thread t = new Thread(r);
            t.setDaemon(false);
            return t;
        });

        CompletableFuture.supplyAsync(CompletableFutureInAction::get, executor)
                .whenComplete((v, t) -> {
    
    
                    Optional.of(v).ifPresent(System.out::println);
                    finished.set(true);
                });

        System.out.println("====i am no ---block----");

    static double get() {
    
    
        try {
    
    
            Thread.sleep(RANDOM.nextInt(10000));
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        double result = RANDOM.nextDouble();
        System.out.println(result);
        return result;
    }
}

//执行结果发现:
  (1)主线程会立即输出====i am no ---block----,而不是先等待异步任务执行完再输出
  (2)异步任务会执行,并打印
  (3)主线程未退出,这是因为executor里的线程是非守护的,线程执行完后,executor还是存在的,
     需要调用executor.shutdown(),主线程才能退出  

4.2 join

CompletableFuture类中的join方法和Future接口中的get有相同的含义,并且也声明在Future接口中,它们唯一的不同是join不会抛出任何检测到的异常。

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

注意到了吗?这里使用了两个不同的Stream流水线,而不是在同一个处理流的流水线上一个接一个地放置两个map操作——这其实是有缘由的。考虑流操作之间的延迟特性,如果你在单一流水线中处理流,发向不同商家的请求只能以同步、顺序执行的方式才会成功。
在这里插入图片描述

  • 上半部分展示了使用单一流水线处理流的过程,我们看到,执行的流程(以虚线标识)是顺序的。事实上,新的CompletableFuture对象只有在前一个操作完全结束之后,才能创建。
  • 与此相反,图的下半部分展示了如何先将CompletableFutures对象聚集到一个列表中(即图中以椭圆表示的部分),让对象们可以在等待其他对象完成操作之前就能启动。

4.3 thenApply、handle、whenComplete

public static void main(String[] args) {
    
    
    CompletableFuture.supplyAsync(() -> 1)
            .thenApply(i -> Integer.sum(i, 10))
            .whenComplete((v, t) -> System.out.println(v));
}
//输出:11
//whenCompleteAsync:会开启一个异步任务执行

CompletableFuture.supplyAsync(() -> 1)
       .handle((v, t) -> Integer.sum(v, 10))
       .whenComplete((v, t) -> System.out.println(v))
//输出:11
//handle相对于thenApply,多了个异步考虑t

4.4 thenRun、thenAccept

CompletableFuture.supplyAsync(() -> 1)
       .handle((v, t) -> Integer.sum(v, 10))
       .whenComplete((v, t) -> System.out.println(v))
       .thenRun(System.out::println);
//输出:11
           --这里有这个空格
//handle相对于thenApply,多了个异步考虑t
//thenRun(Runnable action)会执行一个没有入参数的命令,可能有返回值       


CompletableFuture.supplyAsync(() -> 1)
                .thenApply(i -> Integer.sum(i, 10))
                .thenAccept(System.out::println);
//输出:11
//thenAccept(Consumer<? super T> action) 执行consumer,没有返回值                

4.5 thenCombine、thenAcceptBoth

CompletableFuture.supplyAsync(() -> 1)
                .thenCompose(i -> CompletableFuture.supplyAsync(() -> 10 * i))
                .thenAccept(System.out::println);
//输出:10
//thenCompose:接收前一个任务执行结果作为参数
                
CompletableFuture.supplyAsync(() -> 1)
                .thenCombine(CompletableFuture.supplyAsync(() -> 2.0d), (r1, r2) -> r1 + r2)
                .thenAccept(System.out::println);
//输出:3.0
//thenCombine:第一个参数:新的异步任务,第二个参数:<前一个任务执行的结果, 当前启动的新的异步任务的执行结果>

CompletableFuture.supplyAsync(() -> 1)
                .thenAcceptBoth(CompletableFuture.supplyAsync(() -> 2.0d), (r1, r2) -> {
    
    
                    System.out.println(r1);
                    System.out.println(r2);
                    System.out.println(r1 + r2);
                });
//输出:1
//	   2.0
//	   3.0
//thenAcceptBoth相对于thenCombine的区别是第二个参数是一个consumer               

4.6 runAfterBoth、applyToEither、acceptEither、runAfterEither

public static void main(String[] args) {
    
    
        CompletableFuture.supplyAsync(() -> {
    
    
            System.out.println(Thread.currentThread().getName() + " is running.");
            return 1;
        }).runAfterBoth(CompletableFuture.supplyAsync(() -> {
    
    
            System.out.println(Thread.currentThread().getName() + " is running.");
            return 2;
        }), () -> System.out.println("done"));

    }
//输出:ForkJoinPool.commonPool-worker-5 is running.
//		ForkJoinPool.commonPool-worker-3 is running.
//		done
//runAfterBoth:二个异步任务都执行完成后,在执行其第二个参数的内容
    
CompletableFuture.supplyAsync(() -> {
    
    
            System.out.println(Thread.currentThread().getName() + " is running.");
            try {
    
    
                Thread.sleep(100);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            return 1;
        }).runAfterBoth(CompletableFuture.supplyAsync(() -> {
    
    
            System.out.println(Thread.currentThread().getName() + " is running.");
            return 2;
        }), () -> System.out.println("done"));
//输出:ForkJoinPool.commonPool-worker-5 is running.
//		ForkJoinPool.commonPool-worker-3 is running.
//注意:main线程执行完后,因为异步任务都是守护线程,所以会自动退出,导致未执行完,所以未打印出done 


CompletableFuture.supplyAsync(() -> {
    
    
            System.out.println("I am future 1");
            return CompletableFutureInAction .get();
        }).applyToEither(CompletableFuture.supplyAsync(() -> {
    
    
            System.out.println("I am future 2");
            return CompletableFutureInAction .get();
        }), v -> v * 10)
                .thenAccept(System.out::println);     
//输出:I am future 2
//	   I am future 1
//	   3.414705885166589
//只要有一个任务执行完,就可以利用这个任务执行结果,继续做后面的事情了

CompletableFuture.supplyAsync(() -> {
    
    
            System.out.println("I am future 1");
            return CompletableFutureInAction.get();
        }).acceptEither(CompletableFuture.supplyAsync(() -> {
    
    
            System.out.println("I am future 2");
            return CompletableFutureInAction.get();
        }), v -> System.out.println(v * 10));
//输出:I am future 2
//	   I am future 1
//	   3.414705885166589
//acceptEither相对于applyToEither,第二个参数是一个consumer


//CompletableFuture<Void> acceptEither(CompletableFuture<? extends T> other, Consumer<? super T> block)
//CompletableFuture<Void> runAfterEither(CompletableFuture<?> other, Runnable action)        

4.7 allOf、anyOf

List<CompletableFuture<Double>> collect = Arrays.asList(1, 2, 3, 4)
                .stream()
                .map(i -> CompletableFuture.supplyAsync(CompletableFutureInAction::get))
                .collect(toList());

        CompletableFuture.allOf(collect.toArray(new CompletableFuture[collect.size()]))
                .thenRun(() -> System.out.println("done")
//输出: 0.12233445
//       0.18233745   
//       0.22255445   
//       0.32238445   
//       done  
//allOf:所有CompletableFuture执行完后,才可以执行下一步操作     

List<CompletableFuture<Double>> collect = Arrays.asList(1, 2, 3, 4)
                .stream()
                .map(i -> CompletableFuture.supplyAsync(CompletableFutureInAction::get))
                .collect(toList());

        CompletableFuture.anyOf(collect.toArray(new CompletableFuture[collect.size()]))
                .thenRun(() -> System.out.println("done")
//输出: 0.12233445
//       done  
//anyOf:只要有一个CompletableFuture执行完后,就可以执行下一步操作         

猜你喜欢

转载自blog.csdn.net/weixin_42868638/article/details/110534511