CompletableFuture

1 介绍

CompletableFuture是自JDK1.8版本中引入的新的Future,常用于异步编程之中,所谓异步编程,简单来说就是:“程序运算与应用程序的主线程在不同的线程上完成,并且程序运算的线程能够向主线程通知其进度,以及成功失败与否的非阻塞式编码方式”,这句话听起来与前文中学习的ExecutorService提交异步执行任务并没有多大的区别,但是别忘了,无论是ExecutorService还是CompletionService,都需要主线程主动地获取异步任务执行的最终计算结果。

Google Guava所提供的ListenableFuture解决了这个问题,但是ListenableFuture无法将计算的结果进行异步任务的级联并行运算,甚至构成一个异步任务并行运算的pipeline,但是这一切在CompletableFuture中都得到了很好的支持。

关于Google Guava的Future可参考:Google Guava的Future

public class CompletableFuture<T> implements Future<T>, CompletionStage<T>

CompletableFuture实现了Future接口,所以具备Future的能力,但是还继承了CompletionStage接口

CompletionStage可以简单地认为,该接口是同步或者异步任务完成的某个阶段,它可以是整个任务管道中的最后一个阶段,甚至可以是管道中的某一个阶段,这就意味着可以将多个CompletionStage链接在一起形成一个异步任务链,前置任务执行结束之后会自动触发下一个阶段任务的执行。

CompletableFuture中包含了50多个方法,这一数字在JDK1.9版本中还得到了进一步的增加,这些方法可用于Future之间的组合、合并、任务的异步执行,多个Future的并行计算以及任务执行发生异常的错误处理等。

CompletableFuture的方法中,大多数入参都是函数式接口,比如Supplier、Function、BiFunction、Consumer等,因此熟练理解这些函数式接口是灵活使用CompletableFuture的前提和基础,同时CompletableFuture之所以能够异步执行任务,主要归功于其内部的ExecutorService,默认情况下为ForkJoinPool.commonPool()

2 CompletableFuture的快速入门

2.1 当作Future使用

CompletableFuture首先是一个Future,因此你可以将它当作普通的Future来使用,ExecutorService如果提交了Runnable类型的任务却又期望得到运算结果的返回,则需要在submit方法中将返回值的引用也作为参数传进去。

但是使用CompletableFuture就不需要返回值的引用也作为参数传进去。

 public static void main(String[] args) {
     // 定义Double类型的CompletableFuture
     CompletableFuture<Double> completableFuture
             = new CompletableFuture<>();

     // 提交异步任务
     Executors.newCachedThreadPool().submit(()->{
         try {
             TimeUnit.SECONDS.sleep(5);
             // 设置结果
             completableFuture.complete(Math.PI);
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
     });

     // 非阻塞获取异步任务的计算结果,很明显,此刻异步任务并未执行结束,
     // 那么可以采用默认值的方式(该方法也可以被认为是放弃异步任务的执行结果,但不会取消异步任务的执行)
     // getNow: 如果任务还未结束,可以设置一个默认值,就会返回这个值,这里就是返回0
     Double res = completableFuture.getNow(0d);
     System.out.println("res == 0:" + res.equals(0d));
 }
  1. 需要提前定义CompletableFuture
  2. 在普通的Future中,我们可以通过cancel操作来决定是否取消异步任务的继续执行,同样,在CompletableFuture中也有类似的操作。
public static void main(String[] args) {
    // 定义Double类型的CompletableFuture
    CompletableFuture<Double> completableFuture
            = new CompletableFuture<>();

    // 提交异步任务
    Executors.newCachedThreadPool().submit(()->{
        try {
            TimeUnit.SECONDS.sleep(5);
            // 取消
            completableFuture.cancel(false);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    });

    // get的时候就会抛出异常
    Double res = null;
    try {
        res = completableFuture.get();
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (ExecutionException e) {
        e.printStackTrace();
    }
}

2.2 任务的异步运行

CompletableFuture除了具备Future的基本特性之外,还可以直接使用它执行异步任务,通常情况下,任务的类型为Supplier和Runnable,前者非常类似于Callable接口,可返回指定类型的运算结果,后者则仍旧只是关注异步任务运行本身。

2.2.1 异步执行Supplier类型的任务
@FunctionalInterface
public interface Supplier<T> {
    /**
     * Gets a result.
     * @return a result
     */
    T get();
}

eg:可以直接调用CompletableFuture的静态方法supplyAsync异步执行Supplier类型的任务

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<Double> future =
                CompletableFuture.supplyAsync(() -> Math.PI);
        System.out.println(future.get());

        // supplyAsync方法的另外一个重载方法,允许传入ExecutorService
        CompletableFuture<Double> future1 =
                CompletableFuture.supplyAsync(() -> Math.PI, Executors.newCachedThreadPool());
        System.out.println(future1.get());
    }
2.2.2 异步执行Runnable类型的任务

也可以直接调用CompletableFuture的静态方法runAsync异步执行Runnable类型的任务

public static void main(String[] args) throws ExecutionException, InterruptedException {
    CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("执行runable类型任务");
    });

    // 现在肯定没有完成,上面的任务需要执行3s
    System.out.println("任务是否执行完成:" + future.isDone());
    // 等待4s
    TimeUnit.SECONDS.sleep(4);
    System.out.println("任务是否执行完成:" + future.isDone());

}
任务是否执行完成:false
执行runable类型任务
任务是否执行完成:true

2.3 异步任务链

CompletableFuture还允许将执行的异步任务结果继续交由下一级任务来执行,下一级任务还可以有下一级,以此类推,这样就可以形成一个异步任务链或者任务pipeline。

2.3.1 thenApply: 以同步的方式继续处理上一个异步任务的结果
public <U> CompletableFuture<U> thenApply(
       Function<? super T,? extends U> fn)

thenApply这个方法接收的是一个Function接口

eg:

public static void main(String[] args) throws ExecutionException, InterruptedException {
    ExecutorService executorService = Executors.newFixedThreadPool(3);

    CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
        System.out.println(Thread.currentThread().getName() + " is supplyAsync");
        return "java";
    }, executorService).thenApply((in -> {
        System.out.println(Thread.currentThread().getName() + " is thenApply");
        // 返回上一个任务返回字符串的长度
        return in.length();
    }));
    // 获取结果
    System.out.println(future.get());
}

上面这段代码就是一个简单的任务链,一共就是两个任务:

  1. supplyAsync的计算结果为"Java"
  2. thenApply继续处理"Java",返回字符串的长度

输出结果:可见supplyAsync与thenApply的任务执行是同一个线程,所以是同步执行任务链

pool-1-thread-1 is supplyAsync
pool-1-thread-1 is thenApply
4
2.3.2 thenApplyAsync:以异步的方式继续处理上一个异步任务的结果。
public CompletableFuture<Void> thenAccept(Consumer<? super T> action)

thenAccept接收的是一个Consumer函数接口

public static void main(String[] args) throws ExecutionException, InterruptedException {
    ExecutorService executorService = Executors.newFixedThreadPool(3);

    CompletableFuture.supplyAsync(() -> {
        System.out.println(Thread.currentThread().getName() + " is supplyAsync");
        return "java";
    }, executorService).thenAccept(in->{
        System.out.println(Thread.currentThread().getName() + " is thenAccept");
        System.out.println("长度是: " + in.length());

    });
}

输出:可见supplyAsync与thenApply的任务执行不是是同一个线程,但是是在父线程处理的上一步的结果

pool-1-thread-1 is supplyAsync
main is thenAccept
长度是: 4
2.3.3 thenAcceptAsync:以异步的方式消费上一个异步任务的结果。
 public CompletableFuture<Void> thenAcceptAsync(Consumer<? super T> action)

thenAcceptAsync同样接收的是一个Consumer函数接口

在任务链的末端,如果执行的任务既不想对上一个任务的输出做进一步的处理,又不想消费上一个任务的输出结果

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(3);

        CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName() + " is supplyAsync");
            return "java";
        }, executorService).thenAcceptAsync(in->{
            System.out.println(Thread.currentThread().getName() + " is thenAcceptAsync");
            System.out.println("长度是: " + in.length());

        });
    }
pool-1-thread-1 is supplyAsync
ForkJoinPool.commonPool-worker-1 is thenAcceptAsync
长度是: 4
2.3.4 thenRun:以同步的方式执行Runnable任务。

在任务链的末端,如果执行的任务既不想对上一个任务的输出做进一步的处理,又不想消费上一个任务的输出结果,那么我们可以使用thenRun或者thenRunSync方法来执行Runnable任务。

 public CompletableFuture<Void> thenRun(Runnable action) ;

接收的是一个Runnable接口,这个接口是没有形参的,所以就不可能吧上一个任务的结果传递进来,同样也没有返回值,所以也不会把自己的结果传递给下一个任务

public static void main(String[] args) throws ExecutionException, InterruptedException {
    ExecutorService executorService = Executors.newFixedThreadPool(3);

    CompletableFuture.supplyAsync(() -> {
        System.out.println(Thread.currentThread().getName() + " is supplyAsync");
        return "java";
    }, executorService).thenAcceptAsync(in->{
        System.out.println(Thread.currentThread().getName() + " is thenAcceptAsync");
        System.out.println("长度是: " + in.length());
    }).thenRun(()->{
        System.out.println(Thread.currentThread().getName() + " is thenRun");
        System.out.println("All of task completed. " );
    });
}
2.3.5 thenRunAsync:以异步的方式执行Runnable任务。
public CompletableFuture<Void> thenRunAsync(Runnable action)

// 可以指定自己的线程池
public CompletableFuture<Void> thenRunAsync(Runnable action,
                                            Executor executor)

2.4 合并多个Future

CompletableFuture还允许将若干个Future合并成为一个Future的使用方式,可以通过thenCompose方法或者thenCombine方法来实现多个Future的合并。

2.4.1 thenCompose
public <U> CompletableFuture<U> thenCompose(
        Function<? super T, ? extends CompletionStage<U>> fn

eg:

public static void main(String[] args) throws ExecutionException, InterruptedException {
    // 通过thenCompose将两个Future合并成一个Future

    CompletableFuture<String> future = CompletableFuture
            .supplyAsync(() -> "hello")
            // s为上一个Future的计算结果, 比如这里就是hello
            .thenCompose(s ->
            		// 再执行一个任务,逻辑就是如何合并上一个Future
                    CompletableFuture.supplyAsync(() -> s + " world")
            );
    
    String s = future.get();
    System.out.println(s); // hello world
}
2.4.2 thenCombine
public static void main(String[] args) {
    // 通过thenCombine将两个Future合并成一个Future

    CompletableFuture<String> future
            = CompletableFuture.supplyAsync(() -> "hello")
            .thenCombine(CompletableFuture.supplyAsync(() -> " world"),
                    // s1为第一个Future的计算结果,s2为第二个Future的计算结果
                    (s1, s2) -> s1 + s2);

}

2.5 多Future的并行计算

如果想要多个独立的CompletableFuture同时并行执行,那么我们还可以借助于allOf()方法来完成,其有点类似于ExecutorService的invokeAll批量提交异步任务。

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 定义三个CompletableFuture
        CompletableFuture<String> f1
                = CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "Java";
        });
        CompletableFuture<String> f2
                = CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(6);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "python";
        });
        CompletableFuture<String> f3
                = CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(9);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "go";
        });

        //  批量并行执行,返回值是一个void类型的CompletableFuture
        CompletableFuture<Void> future = CompletableFuture.allOf(f1, f2, f3).thenRun(() -> {
            try {
                System.out.println(f1.isDone() + " and result:" + f1.get());
                System.out.println(f2.isDone() + " and result:" + f2.getNow("无"));
                System.out.println(f3.isDone() + " and result:" + f3.getNow("无"));
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        });
        // 阻塞等待运行结束
        future.get();
    }

如果只想运行一批Future中的一个任务,那么我们又该怎么办呢?只需要用anyOf方法替代allOf方法即可(这一点非常类似于ExecutorService的invokeAny方法),无论是allOf方法还是anyOf方法返回的CompletableFuture类型都是Void类型

2.6 错误处理

CompletableFuture对于异常的处理方式比普通的Future要优雅合理很多,它提供了handle方法,可用于接受上游任务计算过程中出现的异常错误,这样一来,我们便可以不用将错误的处理逻辑写在try…catch…语句块中了,更不需要只能通过Future的get方法调用才能得知异常错误的发生。

public static void main(String[] args) {
    CompletableFuture.<String>supplyAsync(() -> {
        throw new RuntimeException();
    }).handle((t, e) -> {
        if (e != null) {
            // 发生了异常
            System.out.println(Thread.currentThread().getName() + " 发生了异常");
            return "Error";
        }
        return t;
    }).thenAccept(System.out::println);

}

2.7 JDK 9对CompletableFuture的进一步支持

在JDK 9中,Doug Lea继续操刀Java并发包的开发,为CompletableFuture带来了更多新的改变,比如增加了新的静态工厂方法、实例方法,提供了任务处理的延迟和超时支持等。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值