CompletableFuture使用场景一

2,517 阅读6分钟

这个CompletableFuture是JDK1.8版本新引入的类 ,每一个新技术或者新特性或者新模式的的出现,要思考它们解决的是什么问题,为什么会有CompletableFuture这个东东?

下面看下个例子:

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        final Future<?> submit = executorService.submit(() -> {
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        while (!submit.isDone()) {

        }
        System.out.println(" finished");
    }

上述代码,Future设计模式的弊端在于我们需要自己去拿结果。而且Future的get()方法是阻塞的,如果提交一批任务,你并不知道哪个是先执行的,哪个是先返回的.虽然是可以通过CompletionService可以解决这样的一个问题。但是Future get()出来的那个结果,紧接着如果你想在提交一个Callback或者一个Runnable你必须从新定义一个,无法达到级联。但是有没有一种方式就是说我不跟你去要结果,我注册一个回调给你,你通知我,可以实现级联效果。那这就是我们这片文章要讲的ExecutorService和Future的结合CompletableFuture。

Future 的局限性

  1. 不能手动完成 当你写了一个函数,用于通过一个远程API获取一个电子商务产品最新价格。因为这个 API 太耗时,你把它允许在一个独立的线程中,并且从你的函数中返回一个 Future。现在假设这个API服务宕机了,这时你想通过该产品的最新缓存价格手工完成这个Future 。你会发现无法这样做。
  2. Future 的结果在非阻塞的情况下,不能执行更进一步的操作 Future 不会通知你它已经完成了,它提供了一个阻塞的 get() 方法通知你结果。你无法给 Future 植入一个回调函数,当 Future 结果可用的时候,用该回调函数自动的调用 Future 的结果。
  3. 多个 Future 不能串联在一起组成链式调用 有时候你需要执行一个长时间运行的计算任务,并且当计算任务完成的时候,你需要把它的计算结果发送给另外一个长时间运行的计算任务等等。你会发现你无法使用 Future 创建这样的一个工作流。
  4. 不能组合多个 Future 的结果 假设你有10个不同的Future,你想并行的运行,然后在它们运行未完成后运行一些函数。你会发现你也无法使用 Future 这样做。
  5. 没有异常处理 Future API 没有任务的异常处理结构居然有如此多的限制,幸好我们有CompletableFuture,你可以使用 CompletableFuture 达到以上所有目的。

CompletableFuture 实现了 FutureCompletionStage接口,并且提供了许多关于创建,链式调用和组合多个 Future 的便利方法集,而且有广泛的异常处理支持。

Java 1.8 Lamaba表达式或者java.util.function包下的@FunctionInterface注解的接口相关的知识就不多介绍

runAsync方法

 //接收任务,并且是在@link ForkJoinPool#commonPool()}中运行的。最后返回一个新的CompletableFuture
 public static CompletableFuture<Void> runAsync(Runnable runnable) {
        return asyncRunStage(asyncPool, runnable);
    }

thenAccept方法

//接收用户自己实现的一个Consumer的动作,返回一个新的CompletableFuture
   public CompletableFuture<Void> thenAccept(Consumer<? super T> action) {
        return uniAcceptStage(null, action);
    }

whenComplete方法

//接收一个BiConsumer<value,Thrwoable>的动作返回一个新的CompletableFuture
  public CompletableFuture<T> whenComplete(
        BiConsumer<? super T, ? super Throwable> action) {
        return uniWhenCompleteStage(null, action);
    }

supplyAsync方法

    //接受一个Supplier带有返回值的function类型,返回一个新的CompletableFuture
     public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier) {
        return asyncSupplyStage(asyncPool, supplier);
    }

案例一:

public static void main(String[] args) throws InterruptedException {
   		 //一个异步任务
	    CompletableFuture.runAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(5);
                System.out.println("process......");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).whenComplete((v, t) -> System.out.println("DONE"));
     	//Thread.currentThread().join();
}

上述代码是不会执行的,因为它默认创建的是守护线程,main结束它也跟着结束了,因为CompletableFuture不需要关注ExecutorServcie,而是内部自己实现了一个,它代表的仅仅是一个Future。

java.util.concurrent.ForkJoinPool#registerWorker部分代码:
   //注册工作线程时设置为守护线程
 final WorkQueue registerWorker(ForkJoinWorkerThread wt) {
        UncaughtExceptionHandler handler;
        wt.setDaemon(true);   //设置为守护线程                      
        if ((handler = ueh) != null)
            wt.setUncaughtExceptionHandler(handler);
        WorkQueue w = new WorkQueue(this, wt);

案例二:

用 Future的方式来做一个实验:

场景:调用一个查询数据库的方法,拿该方法的返回结果做进一步处理;

//模拟读取数据库(有耗时)
private static int readMysql() {
    int value = ThreadLocalRandom.current().nextInt(20);
    try {
        System.out.println(Thread.currentThread().getName() + " -readMysql start.");
        TimeUnit.SECONDS.sleep(value);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName() + " -readMysql end. value is:"+value);
    return value;
}
//拿到数据库的结果做打印处理 (有耗时)
private static void printData(int data) {
    int value = ThreadLocalRandom.current().nextInt(20);
    try {

        System.out.println(Thread.currentThread().getName() + "-printData  start.");
        TimeUnit.SECONDS.sleep(value);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName() + "- printData  end. " + data);
}
        public static void main(String[] args) throw InterruptedException{
            //创建一个固定的 线程池大小为10
            ExecutorService executorService = Executors.newFixedThreadPool(10);
            //执行任务的集合
        final List<Callable<Integer>> runTasks = IntStream.range(0, 10).boxed().map(i -> (Callable<Integer>) () -> readMysql()).collect(Collectors.toList());
            
        final List<Future<Integer>> futureList = executorService.invokeAll(runTasks);
            
       futureList.stream().map(futures -> {
            try {
               return futures.get(); // 拿到数据库的值 该方法是阻塞的
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
            return null;
           //作进一步处理,将拿到的数据库的值打印出来
        }).parallel().forEach(CompletableFutureExp::printData);
            
        }
执行结果如下:
pool-1-thread-1 -readMysql start.
pool-1-thread-2 -readMysql start.
pool-1-thread-3 -readMysql start.
pool-1-thread-4 -readMysql start.
pool-1-thread-5 -readMysql start.
pool-1-thread-6 -readMysql start.
pool-1-thread-7 -readMysql start.
pool-1-thread-7 -readMysql end.  value is :0
pool-1-thread-8 -readMysql start.
pool-1-thread-10 -readMysql start.
pool-1-thread-9 -readMysql start.
pool-1-thread-4 -readMysql end.  value is :1
pool-1-thread-5 -readMysql end.  value is :3
pool-1-thread-6 -readMysql end.  value is :3
pool-1-thread-10 -readMysql end.  value is :5
pool-1-thread-9 -readMysql end.  value is :7
pool-1-thread-3 -readMysql end.  value is :10
pool-1-thread-8 -readMysql end.  value is :13
pool-1-thread-1 -readMysql end.  value is :17
pool-1-thread-2 -readMysql end.  value is :18
main-printData start.
ForkJoinPool.commonPool-worker-2-printData start.
ForkJoinPool.commonPool-worker-1-printData start.
ForkJoinPool.commonPool-worker-2-printData end. 7
ForkJoinPool.commonPool-worker-1-printData end. 10
ForkJoinPool.commonPool-worker-2-printData start.
ForkJoinPool.commonPool-worker-1-printData start.
ForkJoinPool.commonPool-worker-3-printData start.
ForkJoinPool.commonPool-worker-1-printData end. 3
ForkJoinPool.commonPool-worker-1-printData start.
ForkJoinPool.commonPool-worker-2-printData end. 5
ForkJoinPool.commonPool-worker-2-printData start.
ForkJoinPool.commonPool-worker-1-printData end. 1
ForkJoinPool.commonPool-worker-1-printData start.
ForkJoinPool.commonPool-worker-2-printData end. 13
ForkJoinPool.commonPool-worker-2-printData start.
main-printData end. 0
ForkJoinPool.commonPool-worker-1-printData end. 3
ForkJoinPool.commonPool-worker-2-printData end. 17
ForkJoinPool.commonPool-worker-3-printData end. 18

可以明确的看到,在打印值得时候并不是某一个线程拿到数据库的值立即去执行printData方法,而是要等到所有线程拿到数据库的值才去进行下一步printData操作....这当然不是我们想要的。

案例三:

用 CompletableFuture的方式来做一个实验:

 public static void main(String[] args) throw InterruptedException{
           IntStream.range(0, 10)
                .boxed()
                .forEach(i -> CompletableFuture.supplyAsync(CompletableFutureExp::readMysql)
                .thenAccept(CompletableFutureExp::printData)
                .whenComplete((v, t) -> System.out.println(i + " finished.")));
        Thread.currentThread().join();
}
执行结果如下:
ForkJoinPool.commonPool-worker-1 -readMysql start.
ForkJoinPool.commonPool-worker-2 -readMysql start.
ForkJoinPool.commonPool-worker-3 -readMysql start.
ForkJoinPool.commonPool-worker-2 -readMysql end.  value is :1
ForkJoinPool.commonPool-worker-2-printData start.
ForkJoinPool.commonPool-worker-3 -readMysql end.  value is :8
ForkJoinPool.commonPool-worker-3-printData start.
ForkJoinPool.commonPool-worker-1 -readMysql end.  value is :10
ForkJoinPool.commonPool-worker-1-printData start.
ForkJoinPool.commonPool-worker-2-printData end. 1
1 finished.
ForkJoinPool.commonPool-worker-2 -readMysql start.
ForkJoinPool.commonPool-worker-1-printData end. 10
0 finished.
ForkJoinPool.commonPool-worker-1 -readMysql start.
ForkJoinPool.commonPool-worker-3-printData end. 8
2 finished.
ForkJoinPool.commonPool-worker-3 -readMysql start.
ForkJoinPool.commonPool-worker-1 -readMysql end.  value is :5
ForkJoinPool.commonPool-worker-1-printData start.
ForkJoinPool.commonPool-worker-2 -readMysql end.  value is :17
ForkJoinPool.commonPool-worker-2-printData start.
ForkJoinPool.commonPool-worker-3 -readMysql end.  value is :19
ForkJoinPool.commonPool-worker-3-printData start.
ForkJoinPool.commonPool-worker-1-printData end. 5
4 finished.
ForkJoinPool.commonPool-worker-1 -readMysql start.
ForkJoinPool.commonPool-worker-1 -readMysql end.  value is :1
ForkJoinPool.commonPool-worker-1-printData start.
ForkJoinPool.commonPool-worker-3-printData end. 19
5 finished.
ForkJoinPool.commonPool-worker-3 -readMysql start.
ForkJoinPool.commonPool-worker-2-printData end. 17
3 finished.
ForkJoinPool.commonPool-worker-2 -readMysql start.
ForkJoinPool.commonPool-worker-1-printData end. 1
6 finished.
ForkJoinPool.commonPool-worker-1 -readMysql start.
ForkJoinPool.commonPool-worker-3 -readMysql end.  value is :14
ForkJoinPool.commonPool-worker-3-printData start.
ForkJoinPool.commonPool-worker-1 -readMysql end.  value is :14
ForkJoinPool.commonPool-worker-1-printData start.
ForkJoinPool.commonPool-worker-3-printData end. 14
7 finished.
ForkJoinPool.commonPool-worker-2 -readMysql end.  value is :18
ForkJoinPool.commonPool-worker-2-printData start.
ForkJoinPool.commonPool-worker-1-printData end. 14
9 finished.

可以看到,和案例二的结果不同的是,某一个线程readMysql之后,会立即去printData,这正是我们期望的。

作者实力有限,若有问题,请指出,如有雷同,请联系作者,及时删除,写作不易,未经许可,转载请注明出处,如对您工作有帮助,烦劳高台贵手,请点赞+收藏+关注,未完待续.......