关于Java8Completablefuture的简单使用

905 阅读4分钟

在执行一些需要耗时处理的任务时,希望通过多线程计算,然后异步把各个结果返回,进行合并处理,在Java5可以通过Java线程池配合Future接口实现。

Future Api文档的介绍

  • public interface Future<V>
    

    一个 Future表示异步计算的结果。提供的方法来检查,如果计算完成,等待其完成,并检索结果的计算。结果只能检索使用方法 get当计算完成后,如果有必要,直到阻塞。取消由 cancel方法进行。提供了额外的方法来确定任务是否正常完成或被取消。一旦计算完成,计算不能被取消。如果你想使用一个 Future为可的缘故,但不提供一个可用的结果,你可以声明的形式 Future<?>null类型作为结果的基本任务。

    只有几个方法,比较简单:

返回值方法
booleancancel(boolean mayInterruptIfRunning) 试图取消此任务的执行。
Vget() 等待,如果需要计算完成,然后检索其结果。
Vget(long timeout, TimeUnit unit) 如果需要的话,在大多数给定的计算时间完成,然后检索其结果,如果可用。
booleanisCancelled() 返回 true如果这个任务完成之前取消正常。
booleanisDone() 返回 true如果完成这个任务。

举一个例子

创建两个任务

public class Task2 implements Callable<String> {
    @Override
    public String call() throws Exception {
         /**
         * 计算后返回一个字符串,耗时3秒
         */
        Thread.sleep(3000);
        return "world";
    }
}
public class Task1 implements Callable<String> {
    @Override
    public String call() throws Exception {
        /**
         * 计算后返回一个字符串,耗时1秒
         */
        Thread.sleep(1000);
        return "hello";
    } 
 }

创建一个方法执行上面两个任务,并合并处理结果

     /**
     * 处理任务,合并结果
     *
     * @return
     * @throws ExecutionException
     * @throws InterruptedException
     */
    public static String test() throws ExecutionException, InterruptedException {
        long start = System.currentTimeMillis();
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        /**
         * 
         * sumbmit传入的参数Callable也是函数式接口,也可以写成:
         *
         *     Future<String> task1 = executorService.submit(() -> {
         *         Thread.sleep(1000);
         *         return "hello";
         *    });
         * 
         */
        Future<String> task1 = executorService.submit(new Task1());
        Future<String> task2 = executorService.submit(new Task2());

        /**
         * 处理其他任务,返回结果
         */
        Thread.sleep(3000);
        String s3 = ", I'm coming";

        String s = task1.get();
        String s1 = task2.get();

        long end = System.currentTimeMillis();

        System.out.println("总耗时:" + (end - start) / 1000);

        executorService.shutdown();
        return s + " " + s1 + s3;
    }

执行方法,发现整个方法耗时共3秒,在Future中已经触发了耗时的两个操作,把调用线程解放出来,让它能继续执行其他有价值的工作,不再需要呆呆等待耗时的操作完成

   public static void main(String[] args) throws ExecutionException, InterruptedException {

        String result = test();
        System.out.println(result);
//        总耗时:3
//        hello world, I'm coming
    }

Future接口的局限性

Future接口虽然提供了方法来进行异步操作,让调用线程可以处理其他任务,以及等待任务完成,获取接口计算结果,但是这些特性并不足以编写简洁的代码和满足日常的开发需求,比如

  • 比如几个异步计算合并为一个,而且这个几个异步计算有先后问题

  • 等待所有异步计算的任务完成进行处理

  • 仅处理最先完成的任务(比如调用两个相同的第三方API,哪个API耗时少调用哪个,减少接口耗时)

CompletableFuture 使用

在上述说Future接口的几个局限性,我们先通过几个实例来说明CompletableFuture如何弥补这几种局限性

1、先后顺序

  CompletableFuture<String> stringCompletableFuture = CompletableFuture.supplyAsync(() -> {
            /**
             * 计算返回一个字符串
             */
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "hello";
        }).thenCombine(CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "world";
        }), (x, y) -> {
            return x + y;
        });

        System.out.println("s:"+stringCompletableFuture.join());

        /**
         * s:helloworld
         */

2、仅处理最先完成的任务

        CompletableFuture<String> sCf = CompletableFuture.supplyAsync(() -> {
            /**
             * 计算返回一个字符串
             */
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "a";
        }).applyToEitherAsync(CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "b";
        }), (x) -> {
            return x;
        });
        System.out.println("s:"+sCf.join());
        /**
         * s:a
         */
或者
       CompletableFuture<String> sCf = CompletableFuture.supplyAsync(() -> {
            /**
             * 计算返回一个字符串
             */
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "a";
        });
        CompletableFuture<String> sCf2 = CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "b";
        });
        CompletableFuture<Object> objectCompletableFuture = CompletableFuture.anyOf(sCf, sCf2);

        String join = (String) objectCompletableFuture.join();




3、等待所有异步计算的任务完成进行处理

        long start = System.currentTimeMillis();
        CompletableFuture<String> sCf = CompletableFuture.supplyAsync(() -> {
            /**
             * 计算返回一个字符串
             */
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "a";
        });
        CompletableFuture<String> sCf2 = CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "b";
        });
        CompletableFuture<String> sCf3= CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "c";
        });
        CompletableFuture<Void> voidCompletableFuture = CompletableFuture.allOf(sCf, sCf2,sCf3);
        voidCompletableFuture.join();
        long end = System.currentTimeMillis();
        System.out.println("总耗时:" + (end - start) / 1000);

        System.out.println();
        ArrayList<CompletableFuture<String>> cfList = new ArrayList<>();
        cfList.add(sCf);
        cfList.add(sCf2);
        cfList.add(sCf3);
        cfList.stream().map(CompletableFuture::join).forEach(System.out::println);

        /**
         * 总耗时:5
         * a
         * b
         * c
         */

4、配合线程池处理

CompletableFuture 默认使用线程池ForkJoin池来实现的(线程池中的处理线程数=电脑核数-1,如果生产环境服务器核心数太小,等有大请求量过来,处理逻辑又很复杂,很多线程都在等待执行,会慢慢拖垮了服务器),所以我们使用自定义线程池实现,比如

        ExecutorService executorService = Executors.newFixedThreadPool(5);
        CompletableFuture<String> sCf3= CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "c";
        },executorService);

5、异常处理

CompletableFuture 提供了很多方法满足平时异步计算各种需求,有些方法后缀会带上Async,表示该方法的处理还是给线程池处理,不带则表示由调用线程处理;还提供将异常处理抛出,得到线程内具体的异常提示信息

    ExecutorService executorService = Executors.newFixedThreadPool(5);
        CompletableFuture<String> sCf3 = CompletableFuture.supplyAsync(() -> {
            int i = 1 / 0;
            return "c";
        }, executorService).exceptionally(e -> {
            e.printStackTrace();
            return"1";
        });
        String str = sCf3.join();
        System.out.println("str:"+str);
        executorService.shutdown();