java--CompletableFuture详解

概述

CompletableFuture是对Future的扩展和增强,CompletableFuture实现了Future接口,并在此基础上进行了丰富的扩展,弥补了Future的局限性,同时CompletableFuture实现了对任务编排的能力。这样可以轻松的组织不同任务的运行顺序、规则以及方式。从某种程度上来说,这项能力是它的核心能力。

虽然在以前通过CountDownLatch等工具类也可以实现任务的编排,但需要复杂的逻辑处理,不仅耗费精力并且很难维护

CompletableFuture的继承结构如下:

CompletionStage接口定了任务编排的方法,执行某一阶段,可以向下执行后续阶段。异步执行的,默认线程池是ForkJoinPool.commonPool(),但为了业务之间互不影响,且便于定位问题,强烈推荐使用自定义线程池。

CompletableFuture中默认线程池如下

java 复制代码
// 根据commonPool的并行度来选择,而并行度的计算是在ForkJoinPool的静态代码段完成的
private static final boolean useCommonPool =
    (ForkJoinPool.getCommonPoolParallelism() > 1);
​
private static final Executor asyncPool = useCommonPool ?
    ForkJoinPool.commonPool() : new ThreadPerTaskExecutor();
​

创建异步任务

1、supplyAsync

supplyAsync是创建带有返回值的异步任务,它有如下两个方法,

  • 一个是使用默认线程池ForkJoinPool.commonPool())的方法,
  • 一个是带有自定义线程池的重载方法
swift 复制代码
// 带返回值异步请求,默认线程池
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
 
// 带返回值的异步请求,可以自定义线程池
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)
 

创建异步任务测试:

java 复制代码
package CompletableFuture;
​
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
​
public class Demo11 {
    public static void main(String[] args) {
        //创建异步任务
        //使用默认的线程池
        CompletableFuture<String> comFu1=CompletableFuture.supplyAsync(()->{
            System.out.println("do......Something");
            return "result";
        });
        //等待任务执行完成
        try {
            System.out.println("任务的结果为"+comFu1.get());
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } catch (ExecutionException e) {
            throw new RuntimeException(e);
        }
​
        //使用自定义的线程池
        ExecutorService executorService= Executors.newSingleThreadExecutor();
        CompletableFuture<String> comFu2=CompletableFuture.supplyAsync(()->{
            System.out.println("do......Something");
            return "result";
        },executorService);
        //等待子任务执行完成
        try {
            System.out.println("任务的结果为"+comFu2.get());
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } catch (ExecutionException e) {
            throw new RuntimeException(e);
        }
    }
}

运行结果:

do......Something 任务的结果为result do......Something 任务的结果为result

2、runAsync

runAsync是创建没有返回值的异步任务。它有如下两个方法:

  • 一个是使用默认线程池ForkJoinPool.commonPool())的方法
  • 一个是带有自定义线程池的重载方法
java 复制代码
// 不带返回值的异步请求,默认线程池
public static CompletableFuture<Void> runAsync(Runnable runnable)
 
// 不带返回值的异步请求,可以自定义线程池
public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor)

创建异步任务测试:

csharp 复制代码
public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<Void> cf = CompletableFuture.runAsync(() -> {
            System.out.println("do something....");
        });
 
        //等待任务执行完成
        System.out.println("结果->" + cf.get());
}
 
 
public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 自定义线程池
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        CompletableFuture<Void> cf = CompletableFuture.runAsync(() -> {
            System.out.println("do something....");
        }, executorService);
 
        //等待任务执行完成
        System.out.println("结果->" + cf.get());
}

这创建异步任务的四个方法的区别:

  • runAsync() 以Runnable函数式接口类型为参数 ,没有返回结果supplyAsync() 以Supplier函数式接口类型为参数,返回结果类型为U;Supplier接口的 get()是有返回值的(会阻塞)
  • 使用没有指定Executor的方法时,内部使用ForkJoinPool.commonPool() 作为它的线程池执行异步代码。如果指定线程池,则使用指定的线程池运行。
  • 默认情况下CompletableFuture会使用公共的ForkJoinPool线程池,这个线程池默认创建的线程数是 CPU 的核数(也可以通过 JVM option:-Djava.util.concurrent.ForkJoinPool.common.parallelism 来设置ForkJoinPool线程池的线程数)。如果所有CompletableFuture共享一个线程池,那么一旦有任务执行一些很慢的 I/O 操作,就会导致线程池中所有线程都阻塞在 I/O 操作上,从而造成线程饥饿,进而影响整个系统的性能。所以,强烈建议你要根据不同的业务类型创建不同的线程池,以避免互相干扰

3、获取任务结果的方法

java 复制代码
// 如果完成则返回结果,否则就抛出具体的异常
public T get() throws InterruptedException, ExecutionException 
 
// 最大时间等待返回结果,否则就抛出具体异常
public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException
 
// 完成时返回结果值,否则抛出unchecked异常。为了更好地符合通用函数形式的使用,如果完成此 CompletableFuture所涉及的计算引发异常,则此方法将引发unchecked异常并将底层异常作为其原因
public T join()
 
// 如果完成则返回结果值(或抛出任何遇到的异常),否则返回给定的 valueIfAbsent。
public T getNow(T valueIfAbsent)
 
// 如果任务没有完成,返回的值设置为给定值
public boolean complete(T value)
 
// 如果任务没有完成,就抛出给定异常
public boolean completeExceptionally(Throwable ex) 
 

基本使用也很简单,根据编写代码的实际情况进行选择到底该用那种方式来获取任务的结果

获取结果(join&get)的区别 join()和get()方法都是用来获取CompletableFuture异步之后的返回值。

join()方法抛出的是uncheck异常(即未经检查的异常),不会强制开发者抛出。

get()方法抛出的是经过检查的异常,ExecutionException, InterruptedException 需要用户手动处理(抛出或者 try catch)

异步回调处理

1、thenApply和thenApplyAsync

thenApply表示某个任务执行完成后执行的动作,即回调方法,会将该任务的执行结果即方法返回值作为入参传递到回调方法中,带有返回值。

基本使用:

kotlin 复制代码
package CompletableFuture;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
​
public class Demo22 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<Integer> comfu1=CompletableFuture.supplyAsync(()->{
            System.out.println("comfu1 do something:"+Thread.currentThread().getName());
            return  1;
        });
        CompletableFuture<Integer> comfu2 = comfu1.thenApply((result) ->{
            System.out.println("comfu2 do something:"+Thread.currentThread().getName());
            result=result+2;
            return result;
        });
​
        //等待任务1执行完成
        System.out.println("comfu1结果->" + comfu1.get());
        //等待任务2执行完成
        System.out.println("comfu2结果->" + comfu2.get());
​
        CompletableFuture<Integer> comfu3=CompletableFuture.supplyAsync(()->{
            System.out.println("comfu3 do something:"+Thread.currentThread().getName());
            return  1;
        });
        CompletableFuture<Integer> comfu4 = comfu1.thenApplyAsync((result) ->{
            System.out.println("comfu4 do something:"+Thread.currentThread().getName());
            result=result+2;
            return result;
        });
​
        //等待任务1执行完成
        System.out.println("comfu3结果->" + comfu3.get());
        //等待任务2执行完成
        System.out.println("comfu4结果->" + comfu4.get());
    }
}

运行结果:

comfu1 do something:ForkJoinPool.commonPool-worker-9 comfu2 do something:main comfu1结果->1 comfu2结果->3 comfu3 do something:ForkJoinPool.commonPool-worker-9 comfu3结果->1 comfu4 do something:ForkJoinPool.commonPool-worker-9 comfu4结果->3

区别:

  • thenApply 表示某个任务执行完成后执行的动作,即回调方法,会将该任务的执行结果即方法返回值作为入参传递到回调方法中,有返回值。
  • thenApplyAsync与thenApply的区别在于, 前者是将job2提交到线程池中异步执行,实际执行job2的线程可能是另外一个线程 ,后者是由执行job1的线程立即执行job2,即两个job都是同一个线程执行的
  • 我发现如果第一个任务如果执行时间比较长(比如sleep个几毫秒或者有大的for循环等),那么第二个任务就会由上一个线程执行,否则由main线程执行
  • thenApply实际上不一定是上一个线程执行,还有可能是主线程执行

并且thenApplyAsync可以自定义线程池,默认的使用ForkJoinPool.commonPool()线程池。

2、thenAccept和thenAcceptAsync

thenAccept表示某个任务执行完成后执行的动作,即回调方法,会将该任务的执行结果即方法返回值作为入参传递到回调方法中 ,无返回值。

基本使用:

kotlin 复制代码
package CompletableFuture;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
​
public class Demo33 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<Integer> comfu1=CompletableFuture.supplyAsync(()->{
            System.out.println("comfu1 do something:"+Thread.currentThread().getName());
            return  1;
        });
        CompletableFuture<Void> comfu2 = comfu1.thenAccept((result) -> {
            System.out.println("comfu2 do something:" + Thread.currentThread().getName());
        });
​
        //等待任务1执行完成
        System.out.println("comfu1结果->" + comfu1.get());
        //等待任务2执行完成
        System.out.println("comfu2结果->" + comfu2.get());
​
        CompletableFuture<Integer> comfu3=CompletableFuture.supplyAsync(()->{
            System.out.println("comfu3 do something:"+Thread.currentThread().getName());
            return  1;
        });
        CompletableFuture<Void> comfu4 = comfu1.thenAcceptAsync((result) ->{
            System.out.println("comfu4 do something:"+Thread.currentThread().getName());
        });
​
        //等待任务1执行完成
        System.out.println("comfu3结果->" + comfu3.get());
        //等待任务2执行完成
        System.out.println("comfu4结果->" + comfu4.get());
    }
}

运行结果: comfu1 do something:ForkJoinPool.commonPool-worker-9 comfu2 do something:main comfu1结果->1 comfu2结果->null comfu3 do something:ForkJoinPool.commonPool-worker-9 comfu3结果->1 comfu4 do something:ForkJoinPool.commonPool-worker-9 comfu4结果->null

thenAccep和thenAccepAsync区别在于:

使用thenAccep方法时子任务与父任务使用的是同一个线程,不一定是上一个线程执行,还有可能是主线程执行 ,而thenAccepAsync在子任务中可能是另起一个线程执行任务 ,并且thenAccepAsync可以自定义线程池,默认的使用ForkJoinPool.commonPool()线程池。

3、thenRun和thenRunAsync

thenRun表示任务执行完后执行的动作,即回调方法,无入参,无返回值

基本使用:

kotlin 复制代码
package CompletableFuture;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
​
public class Demo44 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<Integer> comfu1=CompletableFuture.supplyAsync(()->{
            System.out.println("comfu1 do something:"+Thread.currentThread().getName());
            return  1;
        });
        CompletableFuture<Void> comfu2 = comfu1.thenRun(() -> {
            System.out.println("comfu2 do something:" + Thread.currentThread().getName());
        });
​
        //等待任务1执行完成
        System.out.println("comfu1结果->" + comfu1.get());
        //等待任务2执行完成
        System.out.println("comfu2结果->" + comfu2.get());
​
        CompletableFuture<Integer> comfu3=CompletableFuture.supplyAsync(()->{
            System.out.println("comfu3 do something:"+Thread.currentThread().getName());
            return  1;
        });
        CompletableFuture<Void> comfu4 = comfu1.thenRunAsync(() ->{
            System.out.println("comfu4 do something:"+Thread.currentThread().getName());
        });
​
        //等待任务1执行完成
        System.out.println("comfu3结果->" + comfu3.get());
        //等待任务2执行完成
        System.out.println("comfu4结果->" + comfu4.get());
    }
}

运行结果

comfu1 do something:ForkJoinPool.commonPool-worker-9 comfu2 do something:main comfu1结果->1 comfu2结果->null comfu3 do something:ForkJoinPool.commonPool-worker-9 comfu3结果->1 comfu4 do something:ForkJoinPool.commonPool-worker-9 comfu4结果->null

thenRun和thenRunAsync区别在于:

使用thenRun方法时子任务与父任务使用的是同一个线程,不一定是上一个线程执行,还有可能是主线程执行 ,而thenRunAsync在子任务中可能是另起一个线程执行任务,并且thenRunAsync可以自定义线程池,默认的使用ForkJoinPool.commonPool()线程池。

3、whenComplete和whenCompleteAsync

whenComplete是当某个任务执行完成后执行的回调方法,会将执行结果或者执行期间抛出的异常传递给回调方法,如果是正常执行则异常为null,回调方法对应的CompletableFuture的result和该任务一致,如果该任务正常执行,则get 方法返回执行结果,如果是执行异常,那么get方法抛出异常

kotlin 复制代码
package CompletableFuture;
​
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
​
public class Demo55 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<Integer> comfu1=CompletableFuture.supplyAsync(()->{
            System.out.println("comfu1 do something:"+Thread.currentThread().getName());
            // int a=1/0;
            return  1;
        });
        CompletableFuture<Integer> comfu2 = comfu1.whenComplete((result, e) -> {
            System.out.println("上个任务的结果"+result);
            System.out.println("上个任务抛出异常"+e);
            System.out.println(Thread.currentThread()+"comfu2 do something");
        });
​
        //等待任务1执行完成
        System.out.println("comfu1结果->" + comfu1.get());
        //等待任务2执行完成
        System.out.println("comfu2结果->" + comfu2.get());
    }
}

运行结果:

comfu1 do something:ForkJoinPool.commonPool-worker-9 上个任务的结果1 上个任务抛出异常null Thread[main,5,main]comfu2 do something comfu1结果->1 comfu2结果->1

如果解开int a =1/0; 那么就会抛出异常:

上个任务的结果null 上个任务抛出异常java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero Thread[main,5,main]comfu2 do something Exception in thread "main" java.util.concurrent.ExecutionException: java.lang.ArithmeticException: / by zero at java.util.concurrent.CompletableFuture.reportGet(CompletableFuture.java:357) at java.util.concurrent.CompletableFuture.get(CompletableFuture.java:1895) at CompletableFuture.Demo55.main(Demo55.java:20) Caused by: java.lang.ArithmeticException: / by zero at CompletableFuture.Demo55.lambda <math xmlns="http://www.w3.org/1998/Math/MathML"> m a i n main </math>main0(Demo55.java:10) at java.util.concurrent.CompletableFuture <math xmlns="http://www.w3.org/1998/Math/MathML"> A s y n c S u p p l y . r u n ( C o m p l e t a b l e F u t u r e . j a v a : 1590 ) a t j a v a . u t i l . c o n c u r r e n t . C o m p l e t a b l e F u t u r e AsyncSupply.run(CompletableFuture.java:1590) at java.util.concurrent.CompletableFuture </math>AsyncSupply.run(CompletableFuture.java:1590)atjava.util.concurrent.CompletableFutureAsyncSupply.exec(CompletableFuture.java:1582) at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289) at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056) at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692) at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)

whenCompleteAsync和whenComplete区别也是whenCompleteAsync可能会另起一个线程执行任务,并且thenRunAsync可以自定义线程池,默认的使用ForkJoinPool.commonPool()线程池。

4、handle和handleAsync

whenComplete基本一致,区别在于handle的回调方法有返回值。

csharp 复制代码
public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<Integer> cf1 = CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread() + " cf1 do something....");
            // int a = 1/0;
            return 1;
        });
 
        CompletableFuture<Integer> cf2 = cf1.handle((result, e) -> {
            System.out.println(Thread.currentThread() + " cf2 do something....");
            System.out.println("上个任务结果:" + result);
            System.out.println("上个任务抛出异常:" + e);
            return result+2;
        });
 
        //等待任务2执行完成
        System.out.println("cf2结果->" + cf2.get());
}

多任务组合处理

依赖关系

  • thenApply():把前面任务的执行结果,交给后面的Function
  • thenCompose():用来连接两个有依赖关系的任务,结果由第二个任务返回

and集合关系

  • thenCombine():合并任务,有返回值
  • thenAccepetBoth():两个任务执行完成后,将结果交给thenAccepetBoth处理,无返回值
  • runAfterBoth():两个任务都执行完成后,执行下一步操作(Runnable类型任务)

or聚合关系*

  • applyToEither():两个任务哪个执行的快,就使用哪一个结果,有返回值
  • acceptEither():两个任务哪个执行的快,就消费哪一个结果,无返回值
  • runAfterEither():任意一个任务执行完成,进行下一步操作(Runnable类型任务)

并行执行

  • allOf():当所有给定的 CompletableFuture 完成时,返回一个新的 CompletableFuture
  • anyOf():当任何一个给定的CompletablFuture完成时,返回一个新的CompletableFuture

结果处理

  • whenComplete:当任务完成时,将使用结果(或 null)和此阶段的异常(或 null如果没有)执行给定操作
  • exceptionally:返回一个新的CompletableFuture,当前面的CompletableFuture完成时,它也完成,当它异常完成时,给定函数的异常触发这个CompletableFuture的完成

1、thenCombine、thenAcceptBoth 和runAfterBoth

这三个方法都是将两个CompletableFuture组合起来处理,只有两个任务都正常完成时,才进行下阶段任务。

区别:

thenCombine会将两个任务的执行结果作为所提供函数的参数,且该方法有返回值;

thenAcceptBoth同样将两个任务的执行结果作为方法入参,但是无返回值;

runAfterBoth没有入参,也没有返回值。

注意两个任务中只要有一个执行异常,则将该异常信息作为指定任务的执行结果。

ini 复制代码
public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<Integer> cf1 = CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread() + " cf1 do something....");
            return 1;
        });
 
        CompletableFuture<Integer> cf2 = CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread() + " cf2 do something....");
            return 2;
        });
 
        CompletableFuture<Integer> cf3 = cf1.thenCombine(cf2, (a, b) -> {
            System.out.println(Thread.currentThread() + " cf3 do something....");
            return a + b;
        });
 
        System.out.println("cf3结果->" + cf3.get());
}

2、applyToEither、acceptEither和runAfterEither

这三个方法和上面一样也是将两个CompletableFuture组合起来处理,当有一个任务正常完成时,就会进行下阶段任务。

区别:

applyToEither会将已经完成任务的执行结果作为所提供函数的参数,且该方法有返回值;

acceptEither同样将已经完成任务的执行结果作为方法入参,但是无返回值;

runAfterEither没有入参,也没有返回值。

基本使用:

csharp 复制代码
public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<String> cf1 = CompletableFuture.supplyAsync(() -> {
            try {
                System.out.println(Thread.currentThread() + " cf1 do something....");
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "cf1 任务完成";
        });
 
        CompletableFuture<String> cf2 = CompletableFuture.supplyAsync(() -> {
            try {
                System.out.println(Thread.currentThread() + " cf2 do something....");
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "cf2 任务完成";
        });
 
        CompletableFuture<String> cf3 = cf1.applyToEither(cf2, (result) -> {
            System.out.println("接收到" + result);
            System.out.println(Thread.currentThread() + " cf3 do something....");
            return "cf3 任务完成";
        });
 
        System.out.println("cf3结果->" + cf3.get());
}

3、allOf / anyOf

allOf:CompletableFuture是多个任务都执行完成后才会执行,只有有一个任务执行异常,则返回的CompletableFuture执行get方法时会抛出异常,如果都是正常执行,则get返回null。

anyOf :CompletableFuture是多个任务只要有一个任务执行完成 ,则返回的CompletableFuture,执行get方法时会抛出异常,如果都是正常执行,则get返回执行完成任务的结果。

csharp 复制代码
​
public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<String> cf1 = CompletableFuture.supplyAsync(() -> {
            try {
                System.out.println(Thread.currentThread() + " cf1 do something....");
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("cf1 任务完成");
            return "cf1 任务完成";
        });
 
        CompletableFuture<String> cf2 = CompletableFuture.supplyAsync(() -> {
            try {
                System.out.println(Thread.currentThread() + " cf2 do something....");
                int a = 1/0;
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("cf2 任务完成");
            return "cf2 任务完成";
        });
 
        CompletableFuture<String> cf3 = CompletableFuture.supplyAsync(() -> {
            try {
                System.out.println(Thread.currentThread() + " cf2 do something....");
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("cf3 任务完成");
            return "cf3 任务完成";
        });
 
        CompletableFuture<Void> cfAll = CompletableFuture.allOf(cf1, cf2, cf3);
        System.out.println("cfAll结果->" + cfAll.get());
}
​
相关推荐
databook13 分钟前
Just:告别 Makefile 的现代命令行任务运行器
后端·命令行
这里有鱼汤16 分钟前
Python初学者常犯的错误汇总,建议收藏
后端·python
程序员爱钓鱼16 分钟前
Go语言中的文件与IO:bufio 和 scanner
后端·google·go
why技术25 分钟前
编码之道,道心破碎。
后端·面试
寻月隐君25 分钟前
Solana 开发进阶:链上事件到链下解析全攻略
后端·rust·web3
看她闹~30 分钟前
javacv添加字幕 剧中显示
java·javacv·字幕
风象南1 小时前
Spring Boot 的 3 种动态 Bean 注入技巧
java·spring boot·后端
移动开发者1号1 小时前
Kotlinx序列化多平台兼容性详解
android·java·kotlin
东阳马生架构10 小时前
商品中心—6.商品考核系统的技术文档
java
晴空月明11 小时前
Java 内存模型与 Happens-Before 关系深度解析
java