JAVA 8 CompletableFuture详解

1 JAVA多线程的实现方式

多线程的使用可以有Runnable及Callable

1 Runnable

最简单、最熟悉的方案,实现该接口需要重写run方法,缺点是没有返回值

        Runnable runnable = () -> System.out.println("runnable 方法");
        new Thread(runnable).start();

2 Callable

该接口中有一个V call方法,可以返回泛型值V并抛出异常

//传递返回值
Callable<String> callable = () -> "Callable 方法";
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        Future<String> future = executorService.submit(callable);
        System.out.println(future.get());
        executorService.shutdown();
//抛出异常,该操作在runnable中是不可行的
Callable<String> callable = new Callable<String>() {
            @Override
            public String call() throws Exception {
                throw new InterruptedException("sss");
            }
        };

2 Future与CompletableFuture

1 Future

在上面Callable的例子中,我们可以看到使用Future去接callable执行的结果,
然后使用future.get()得到返回值

Future的本义是“未来”,表明我们在程序中需要使用callable计算结果时,可以随时调用future.get()进行获取,
如果计算完成了就直接返回结果,没有计算完就进行阻塞,直到计算完成。

这样可以让我们的主线程与其他线程分离,我们只需关注计算的结果。

Future接口包含以下方法:

get():获取结果(可能会等待)
get(long timeout, TimeUnit unit):获取结果,但只等待指定的时间;
cancel(boolean mayInterruptIfRunning):取消当前任务;
isDone():判断任务是否已完成。

2 CompletableFuture

Future看起来比较完美了,但是仍然存在一些问题:

使用Future获取异步执行结果时候,要么调用get()阻塞等待,要么使用isDone()不停轮询,这两种方法会造成主线程的被迫等待。

于是java 8中引入了CompletableFuture,对Future进行改进,可以传入一个回调对象,当异步任务完成或者发生异常时,
可以调用回调方法。

下面的例子说明Future会阻塞主线程

        Long startTime = System.currentTimeMillis();
        System.out.println("主线程开始运行");
        Callable<String> callable = () -> {Thread.sleep(2000); return "我是结果";};
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        System.out.println("=====异步线程开始运行=====");
        Future<String> future = executorService.submit(callable);
        System.out.println(future.get());
        executorService.shutdown();
        System.out.println("=====异步线程执行结束=====");
        Long endTime = System.currentTimeMillis();
        System.out.println("主线程结束,耗时为"+ (endTime - startTime) + "ms");

结果是:

主线程开始运行
=====异步线程开始运行=====
我是结果
=====异步线程执行结束=====
主线程结束,耗时为2041ms

如果改成CompletableFuture:

主线程不必卡在get()上等待,可以去做其他事情,最后获取异步计算的执行结果

 Long startTime = System.currentTimeMillis();
        System.out.println("主线程开始运行");
        Supplier<String> supplier = () -> {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "我是结果";};
        System.out.println("=====异步线程开始运行=====");
        CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(supplier);
        completableFuture.thenAccept(System.out::println);
        System.out.println("=====异步线程执行结束=====");
        Long endTime = System.currentTimeMillis();
        //主线程做其他事情
        Thread.sleep(3000);
        System.out.println("主线程结束,耗时为"+ (endTime - startTime) + "ms");

输出结果为:

主线程开始运行
=====异步线程开始运行=====
=====异步线程执行结束=====
我是结果
主线程结束,耗时为38ms

CompletableFuture的最大优势是链式调用,多个CompletableFuture可以串行执行。在异步完成A任务后,使用A的结果
继续完成B任务,最后再把结果返回主线程。

以下面的代码为例:

功能是异步生成1~5之间的随机数后加10。completableFuture1负责生产随机数,completableFuture2负责将随机数加10。
两个任务互相衔接,最后输出结果。

        Supplier<Integer> supplier = () -> ThreadLocalRandom.current().nextInt(1, 6);
        CompletableFuture<Integer> completableFuture1 = CompletableFuture.supplyAsync(supplier);
        CompletableFuture<Integer> completableFuture2 = completableFuture1.thenApplyAsync((result)-> result+10 );
        completableFuture2.thenAccept(System.out::println);

多个CompletableFuture也可以并行执行。假如任务C需要任务A、B中的任意一个结果,那么就可以比较A和B哪个最先完成,然后立即传递给C。

以下面的代码为例:

两个线程分别生成苹果和香蕉,而小猴子最后吃到的是苹果。

        Supplier<String> apple = () -> {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "苹果";
        };
        Supplier<String> banana = () -> {
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "香蕉";};

        CompletableFuture<String> completableFuture1 = CompletableFuture.supplyAsync(apple);
        CompletableFuture<String> completableFuture2 = CompletableFuture.supplyAsync(banana);

        CompletableFuture<Object> completableFuture = CompletableFuture.anyOf(completableFuture1,completableFuture2);
        completableFuture.thenApplyAsync((result)-> "小猴子饿了,要吃:" + result).thenAccept(System.out::println);
        Thread.sleep(3000);

结果为:小猴子饿了,要吃:苹果

CompletableFuture异步调用多线程的方法:

上面我们大量使用了带有返回值的.supplyAsync发起异步调用,CompletableFuture中还有这些方法:

supplyAsync(Supplier supplier)

参数为Supplier类型的对象,返回CompletableFuture类型的对象。
其中Supplier是一种函数式接口,没有任何参数,只是返回一个结果() -> {xxxxx};

supplyAsync(Supplier supplier,Executor executor)

增加了Executor参数,可以使用线程池中的线程进行计算

runAsync(Runnable runnable)

runAsync 将 Runnable 作为输入参数并返回 CompletableFuture,这意味着它不返回任何结果。

runAsync(Runnable runnable,Executor executor)

增加了Executor参数,可以使用线程池中的线程进行计算

CompletableFuture可以指定异步处理流程:

thenAccept()处理正常结果;

exceptional()处理异常结果;

thenApplyAsync()用于串行化另一个CompletableFuture;

anyOf()和allOf()用于并行化多个CompletableFuture。

参考文献:

1 https://blog.hackajob.co/concurrency-and-improvements-in-java-8-part-1/

2 http://moguhu.com/article/detail?articleId=43

3 https://www.liaoxuefeng.com/wiki/1252599548343744/1306581182447650

4 https://stackoverflow.com/questions/60159153/completablefuture-runasync-vs-supplyasync-when-to-choose-one-over-the-other

你可能感兴趣的:(java,java8,java,并发)