CompletableFuture 学习

整理学习加深印象,文章转载自异步编程利器:CompletableFuture详解 |Java 开发实战

创建异步任务

CompletableFuture 创建异步任务,一般有 supplyAsyncrunAsync 两个方法

supplyAsync执行CompletableFuture任务,没有返回值

//使用默认内置线程池ForkJoinPool.commonPool(),根据supplier构建执行任务
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
//自定义线程,根据supplier构建执行任务
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)

runAsync执行CompletableFuture任务,有返回值

//使用默认内置线程池ForkJoinPool.commonPool(),根据runnable构建执行任务
public static CompletableFuture<Void> runAsync(Runnable runnable) 
//自定义线程,根据runnable构建执行任务
public static CompletableFuture<Void> runAsync(Runnable runnable,  Executor executor)

任务异步回调

image

1.thenRun/thenRunAsync

public CompletableFuture<Void> thenRun(Runnable action);
public CompletableFuture<Void> thenRunAsync(Runnable action);

CompletableFuturethenRun方法,通俗点讲就是,做完第一个任务后,再做第二个任务。某个任务执行完成后,执行回调方法;但是前后两个任务没有参数传递,第二个任务也没有返回值

public static void main(String[] args) throws InterruptedException, ExecutionException {
    CompletableFuture<String> firstFuture = CompletableFuture.supplyAsync(() -> {
        System.out.println("先执行第一个CompletableFuture方法任务");
        return "First Function";
    });

    CompletableFuture<Void> thenRun = firstFuture.thenRun(() -> System.out.println("接着执行第二个任务"));

    System.out.println("thenRun.get() = " + thenRun.get());
}

// 输出
// 先执行第一个CompletableFuture方法任务
// 接着执行第二个任务
// thenRun.get() = null

thenRun 和thenRunAsync的区别
CompletableFuture调用带Async的方法是异步执行,这个方法可以传入线程池参数,传了的话就是用自定义线程池,不传使用Fork-join线程池。调用不带Async的方法是在主线程中执行

如果你执行第一个任务的时候,传入了一个自定义线程池:

  • 调用thenRun方法执行第二个任务时,则第二个任务和第一个任务是共用同一个线程池
  • 调用thenRunAsync执行第二个任务时,则第一个任务使用的是你自己传入的线程池第二个任务使用的是ForkJoin线程池

TIPS: 后面介绍的thenAccept和thenAcceptAsync,thenApply和thenApplyAsync等,它们之间的区别也是这个哈。

2.thenAccept/thenAcceptAsync

CompletableFuturethenAccept方法表示,第一个任务执行完成后,执行第二个回调方法任务,会将该任务的执行结果,作为入参,传递到回调方法中,但是回调方法是没有返回值的。

public static void main(String[] args) throws InterruptedException, ExecutionException {
    CompletableFuture<String> firstFuture = CompletableFuture.supplyAsync(() -> {
        System.out.println("先执行第一个CompletableFuture方法任务");
        return "First Function";
    });

    CompletableFuture<Void> thenAccept = firstFuture.thenAccept((item) -> {
        String str = "First Function";
        if (Objects.equals(str, item)) {
            System.out.println("equals");
        }
        System.out.println("finish");
    });
    System.out.println("thenAccept.get() = " + thenAccept.get());
}

// 先执行第一个CompletableFuture方法任务
// equals
// finish
// thenAccept.get() = null

3.thenApply/thenApplyAsync

CompletableFuturethenApply方法表示,第一个任务执行完成后,执行第二个回调方法任务,会将该任务的执行结果,作为入参,传递到回调方法中,并且回调方法是有返回值的

public static void main(String[] args) throws InterruptedException, ExecutionException {
    CompletableFuture<String> firstFuture = CompletableFuture.supplyAsync(() -> {
        System.out.println("先执行第一个CompletableFuture方法任务");
        return "First Function";
    });

    CompletableFuture<String> thenApply = firstFuture.thenApply((item) -> {
        String str = "First Function";
        if (Objects.equals(str, item)) {
            System.out.println("equals");
        }
        return "finish";
    });
    System.out.println("thenApply.get() = " + thenApply.get());
}

// 先执行第一个CompletableFuture方法任务
// equals
// thenApply.get() = finish

4.exceptionally

CompletableFutureexceptionally方法表示,某个任务执行异常时,执行的回调方法;并且有抛出异常作为参数,传递到回调方法

public static void main(String[] args) throws InterruptedException, ExecutionException {
    CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
        System.out.println("当前线程名称:" + Thread.currentThread().getName());
        throw new RuntimeException("发生运行时异常");
    });

    CompletableFuture<String> exceptionally = future.exceptionally((e) -> {
        System.out.println("程序发生异常,异常原因:" + e.getMessage());
        return "程序发生异常";
    });

    System.out.println("exceptionally.get() = " + exceptionally.get());
}

// 当前线程名称:ForkJoinPool.commonPool-worker-9
// 程序发生异常,异常原因:java.lang.RuntimeException: 发生运行时异常
// exceptionally.get() = 程序发生异常

5.whenComplete方法

CompletableFuturewhenComplete方法表示,某个任务执行完成后,执行的回调方法,无返回值;并且whenComplete方法返回的CompletableFutureresult是上个任务的结果

public static void main(String[] args) throws InterruptedException, ExecutionException {
    CompletableFuture<String> firstFuture = CompletableFuture.supplyAsync(() -> {
        System.out.println("当前线程名称:" + Thread.currentThread().getName());
        try {
            Thread.sleep(2000L);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "First Function";
    });

    CompletableFuture<String> future = firstFuture.whenComplete((item, throwable) -> {
        System.out.println("当前线程名称:" + Thread.currentThread().getName());
        System.out.println("上个任务执行完啦,还把" + item + "传过来");
        System.out.println("finish");
    });

    System.out.println("future.get() = " + future.get());
}

// 当前线程名称:ForkJoinPool.commonPool-worker-9
// 当前线程名称:ForkJoinPool.commonPool-worker-9
// 上个任务执行完啦,还把First Function传过来
// finish
// future.get() = First Function

6.handle方法

CompletableFuturehandle方法表示,某个任务执行完成后,执行回调方法,并且是有返回值的;并且handle方法返回的CompletableFutureresult是回调方法执行的结果

public static void main(String[] args) throws InterruptedException, ExecutionException {
    CompletableFuture<String> firstFuture = CompletableFuture.supplyAsync(() -> {
        System.out.println("当前线程名称:" + Thread.currentThread().getName());
        try {
            Thread.sleep(2000L);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "First Function";
    });

    CompletableFuture<String> future = firstFuture.handle((item, throwable) -> {
        System.out.println("当前线程名称:" + Thread.currentThread().getName());
        System.out.println("上个任务执行完啦,还把" + item + "传过来");
        return "finish";
    });

    System.out.println("future.get() = " + future.get());
}

// 当前线程名称:ForkJoinPool.commonPool-worker-9
// 当前线程名称:ForkJoinPool.commonPool-worker-9
// 上个任务执行完啦,还把First Function传过来
// future.get() = finish

多个任务组合处理

image

AND组合关系

image

thenCombine / thenAcceptBoth / runAfterBoth都表示:将两个CompletableFuture组合起来,只有这两个都正常执行完了,才会执行某个任务。

区别在于:

  • thenCombine:会将两个任务的执行结果作为方法入参,传递到指定方法中,且有返回值
  • thenAcceptBoth: 会将两个任务的执行结果作为方法入参,传递到指定方法中,且无返回值
  • runAfterBoth 不会把执行结果当做方法入参,且没有返回值
public static void main(String[] args) throws InterruptedException, ExecutionException {
    CompletableFuture<String> first = CompletableFuture.completedFuture("第一个异步任务");
    ExecutorService executor = Executors.newFixedThreadPool(10);
    CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "第二个异步任务", executor)
            .thenCombineAsync(first, (secondTask, firstTask) -> {
                System.out.println(firstTask);
                System.out.println(secondTask);
                return "两个异步任务的组合";
            }, executor);

    System.out.println("future.get() = " + future.get());
    executor.shutdown();
}

// 输出
// 第一个异步任务
// 第二个异步任务
// future.get() = 两个异步任务的组合

OR组合关系

image

applyToEither / acceptEither / runAfterEither 都表示:将两个CompletableFuture组合起来,只要其中一个执行完了,就会执行某个任务。

区别在于:

  • applyToEither:会将已经执行完成的任务,作为方法入参,传递到指定方法中,且有返回值
  • acceptEither: 会将已经执行完成的任务,作为方法入参,传递到指定方法中,且无返回值
  • runAfterEither: 不会把执行结果当做方法入参,且没有返回值。
public static void main(String[] args) throws InterruptedException, ExecutionException {
    // 第一个异步任务,休眠2秒,保证它执行晚点
    CompletableFuture<String> first = CompletableFuture.supplyAsync(() -> {
        try {
            Thread.sleep(2000L);
            System.out.println("执行完第一个异步任务");
        } catch (Exception e) {
            return "第一个任务异常";
        }
        return "第一个异步任务";
    });

    ExecutorService executor = Executors.newSingleThreadExecutor();
    CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> {
                System.out.println("执行完第二个任务");
                return "第二个任务";
            }, executor)
            //第三个任务
            .acceptEitherAsync(first, System.out::println, executor);
    System.out.println("future.get() = " + future.get());
    executor.shutdown();
}

// 输出
// 执行完第二个任务
// 第二个任务

AllOf

所有任务都执行完成后,才执行 allOf返回的CompletableFuture。如果任意一个任务异常,allOf的CompletableFuture,执行get方法,会抛出异常

public static void main(String[] args) throws InterruptedException, ExecutionException {
    CompletableFuture<Void> firstFuture = CompletableFuture.runAsync(() -> {
        System.out.println("第一个任务执行完了");
    });
    CompletableFuture<Void> secondFuture = CompletableFuture.runAsync(() -> {
        System.out.println("第二个任务执行完了");
    });
    CompletableFuture<Void> allOfFuture = CompletableFuture.allOf(firstFuture, secondFuture).whenComplete((m, k) -> {
        System.out.println("finish");
    });
}

// 输出
// 第一个任务执行完了
// 第二个任务执行完了
// finish

AnyOf

public static void main(String[] args) throws InterruptedException, ExecutionException {
    CompletableFuture<Void> firstFuture = CompletableFuture.runAsync(()->{
        try {
            Thread.sleep(3000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("第一个任务执行完了");
    });
    CompletableFuture<Void> secondFuture = CompletableFuture.runAsync(() -> System.out.println("第二个任务执行完了"));
    CompletableFuture<Object> anyOfFuture = CompletableFuture.anyOf(firstFuture, secondFuture)
            .whenComplete((m,k)-> System.out.println("finish"));
    anyOfFuture.join();
}

// 输出
// 第二个任务执行完了
// finish

thenCompose

thenCompose方法会在某个任务执行完成后,将该任务的执行结果,作为方法入参,去执行指定的方法。该方法会返回一个新的CompletableFuture实例

  • 如果该CompletableFuture实例的result不为null,则返回一个基于该result新的CompletableFuture实例;
  • 如果该CompletableFuture实例为null,然后就执行这个新任务
public static void main(String[] args) throws InterruptedException, ExecutionException {
    CompletableFuture<String> firstFuture = CompletableFuture.completedFuture("第一个任务");
    // 第二个异步任务
    ExecutorService executor = Executors.newSingleThreadExecutor();
    CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "第二个任务", executor)
            .thenComposeAsync(data -> {
                System.out.println(data);
                return firstFuture;
            }, executor);
    System.out.println("future.join() = " + future.join());
    executor.shutdown();
}

// 输出
// 第二个任务
// future.join() = 第一个任务

CompletableFuture使用有哪些注意点

CompletableFuture 使我们的异步编程更加便利的、代码更加优雅的同时,我们也要关注下它,使用的一些注意点。

1.Future需要获取返回值,才能获取异常信息

ExecutorService executorService = new ThreadPoolExecutor(5, 10, 5L,
    TimeUnit.SECONDS, new ArrayBlockingQueue<>(10));
CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> {
      int a = 0;
      int b = 666;
      int c = b / a;
      return true;
   },executorService).thenAccept(System.out::println);

 //如果不加 get()方法这一行,看不到异常信息
 //future.get();

2. CompletableFuture的get()方法是阻塞的。

CompletableFutureget()方法是阻塞的,如果使用它来获取异步调用的返回值,需要添加超时时间~

// 反例
CompletableFuture.get();
// 正例
CompletableFuture.get(5, TimeUnit.SECONDS);

3. 默认线程池的注意点

CompletableFuture代码中又使用了默认的线程池,处理的线程个数是电脑CPU核数-1。在大量请求过来的时候,处理逻辑复杂的话,响应会很慢。一般建议使用自定义线程池,优化线程池配置参数。

4. 自定义线程池时,注意饱和策略

CompletableFutureget()方法是阻塞的,我们一般建议使用future.get(3, TimeUnit.SECONDS)。并且一般建议使用自定义线程池。
但是如果线程池拒绝策略是DiscardPolicy或者DiscardOldestPolicy,当线程池饱和时,会直接丢弃任务,不会抛弃异常。因此建议,CompletableFuture线程池策略最好使用AbortPolicy,然后耗时的异步线程,做好线程池隔离。

posted @ 2023-11-11 10:50  Kllin  阅读(24)  评论(0编辑  收藏  举报