JUC基础11——CompletableFuture

322 阅读5分钟

CompletableFuture

Future 和 CompletableFuture

Future 接口的不足

在使用Future执行异步任务时,存在以下几个缺点:

  • Future不支持非阻塞调用,且只提供了 get() 方法来获取结果,在并发情况下只有等待所有线程执行完毕之后才能获取结果;通过轮询的方式调用 isDone() 方法判断是否执行完成容易导致cpu空转,造成系统资源浪费
  • FutrueTask获取结果只能获取一次,如果需要多次访问计算结果则需要重新计算执行任务,会导致性能下降
  • Future没有异常处理机制,其接口中没有处理异常的方法,如果执行任务过程中出现了异常,不好精准定位
  • Future 不支持链式调用,也就是说第二个任务不能获取第一个任务执行后的结果再去执行
  • Future只能计算单个任务,不支持多个Future合并,如果我们需要有10个Future合并,在执行所有Futrue任务完成之后再进行某些操作,是没法通过Future实现的

CompletableFuture 概述

CompletableFuture是Java 8引入的一个新特性,它提供了一种异步编程的方式,使得开发者能够编写非阻塞的代码。CompletableFuture实现了Future接口和CompletionStage接口,是对Future的功能增强 其类架构如下

image.png

CompletableFuture 有以下几个方面的特性:

  • 异步执行:CompletableFuture 可以提交异步任务,使用CompletableFuture.runAsync() 方法执行没有返回值的异步任务,或者使用 CompletableFuture.supplyAsync() 方法执行有返回值的任务
  • 回调函数:CompletableFuture 支持注册回调函数来处理异步任务完成时的结果。可以使用 thenApply()、thenAccepty、thenRun() 等方法在任务完成后执行相应的操作,并将上一步结果传给下一步
  • 组合操作:CompletableFuture 提供了一系列的方法来组合多个CompletableFuture ,以实现复杂的异步操作链
  • 异常处理:CompletabeFuture 允许对异常进行处理,可以使用exceptionally() 方法来捕获异常并返回一个默认值,或者使用handle() 方法来处理异常并返回一个新的CompletableFuture
  • 超时处理:CompletableFuture 提供了CompleteOnTimeOut() 和 orTimeout() 方法,用于设置任务的超时时间,并在超时时执行相应的操作

CompletableFuture 的使用

创建一个异步任务 核心的四个静态方法


/**
 * 有返回值的异步任务.
 *
 * @param runnable the action to run before completing the
 * returned CompletableFuture
 * @return the new CompletableFuture
 */

public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier) {
    return asyncSupplyStage(asyncPool, supplier);
}

/**
 * 有返回值的异步任务.
 *
 * @param runnable the action to run before completing the
 * returned CompletableFuture
 * @param executor the executor to use for asynchronous execution
 * @return the new CompletableFuture
 */
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier,Executor executor){
    return asyncSupplyStage(screenExecutor(executor), supplier);
}

/**
 * 无返回值的异步任务
 * @param runnable the action to run before completing the
 * returned CompletableFuture
 * @return the new CompletableFuture
 */
public static CompletableFuture<Void> runAsync(Runnable runnable) {
    return asyncRunStage(asyncPool, runnable);
}


/**
 * 无返回值的异步任务
 * @param runnable the action to run before completing the
 * returned CompletableFuture
 * @param executor the executor to use for asynchronous execution
 * @return the new CompletableFuture
 */

public static CompletableFuture<Void> runAsync(Runnable runnable,Executor executor) {
    return asyncRunStage(screenExecutor(executor), runnable);
}

常用方法

获得结果和触发计算
public T get()

public T get(long timeout,TimeUnit unit)

//和get一样的作用,只是不需要抛出异常
public T join()

//计算完成就返回正常值,否则返回备态值(传入的参数),立即获取结果不阻塞
public T getNow(T valuelfAbsent) 

//是否打断get方法立即返回括号值
public boolean complete(T value)
对计算结果进行处理

thenApply:计算结果存在依赖关系,这两个线程串行化---->由于存在依赖关系(当前步错,不走下一步),当前步骤有异常的话就叫停

public <U> CompletableFuture<U> thenApply(
    Function<? super T,? extends U> fn) {
    return uniApplyStage(null, fn);
}


handle:计算结果存在依赖关系,这两个线程串行化---->有异常也可以往下走一步

public <U> CompletableFuture<U> handle(
    BiFunction<? super T, Throwable, ? extends U> fn) {
    return uniHandleStage(null, fn);
}

使用案例演示

异步任务创建

无返回值的异步任务:runAsync
public static void main(String[] args) throws ExecutionException, InterruptedException {
    ThreadPoolExecutor executor = new ThreadPoolExecutor(3,5,2,TimeUnit.SECONDS,new ArrayBlockingQueue<>(5));
    CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(()->{
        try {
            System.out.println("子线程线程:"+Thread.currentThread().getName()+" 启动干活");
            TimeUnit.SECONDS.sleep(1);
            System.out.println("子线程线程:"+Thread.currentThread().getName()+" 干活完成");

        }catch (Exception e){
            e.printStackTrace();
        }
    },executor);
    System.out.println("获取到的结果是:"+completableFuture.get());
    executor.shutdown();
}
有返回值的异步任务:supplyAsync
public static void main(String[] args) throws ExecutionException, InterruptedException {
   ThreadPoolExecutor executor = new ThreadPoolExecutor(3,5,2,TimeUnit.SECONDS,new ArrayBlockingQueue<>(5));
   CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(()->{
       try {
           System.out.println("子线程:"+Thread.currentThread().getName()+" 启动干活");
           TimeUnit.SECONDS.sleep(1);
           System.out.println("子线程:"+Thread.currentThread().getName()+" 干活结束");
       } catch (InterruptedException e) {
           e.printStackTrace();
       }
      return "complete task";
   },executor);

   System.out.println("执行后的结果是:"+completableFuture.get());
   executor.shutdown();

}

常用方法使用

对计算结果处理(thenApply和handle):

thenApply

public static void main(String[] args) throws Exception {
    ThreadPoolExecutor executor = new ThreadPoolExecutor(3,5,1,TimeUnit.SECONDS,new ArrayBlockingQueue<>(3));
    CompletableFuture<Integer> future = CompletableFuture.supplyAsync(()->{
        System.out.println("子线程执行任务:"+Thread.currentThread().getName());
        int d = 10/0;
        return 10;
    },executor).thenApply(v ->{
        System.out.println(Thread.currentThread().getName()+":执行第二步处理:");
        return v+2;
    });
    System.out.println(Thread.currentThread().getName() + "------主线程先去做其他事情");
    System.out.println("获取到的最终执行结果是:"+future.get());
    executor.shutdown();
}

执行结果:计算结果存在依赖关系,这两个线程串行化---->由于存在依赖关系(当前步错,不走下一步),当前步骤有异常的话就叫停

子线程执行任务:pool-1-thread-1
main------主线程先去做其他事情
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 com.avgrado.demo.thread.CompleteFutureDemo.main(CompleteFutureDemo.java:23)
Caused by: java.lang.ArithmeticException: / by zero
	at com.avgrado.demo.thread.CompleteFutureDemo.lambda$main$0(CompleteFutureDemo.java:16)
	at java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1590)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)

handle:计算结果存在依赖关系,这两个线程串行化---->有异常也可以往下走一步

public static void main(String[] args) throws Exception {
    ThreadPoolExecutor executor = new ThreadPoolExecutor(3,5,1,TimeUnit.SECONDS,new ArrayBlockingQueue<>(3));
    CompletableFuture<Integer> future = CompletableFuture.supplyAsync(()->{
        System.out.println("子线程执行任务:"+Thread.currentThread().getName());
        int d = 10/0;
        return 10;
    },executor).handle((v,e)->{
        System.out.println(Thread.currentThread().getName()+":执行第二步处理:");
        return v+2;
    });
    System.out.println(Thread.currentThread().getName() + "------主线程先去做其他事情");
    System.out.println("获取到的最终执行结果是:"+future.get());
    executor.shutdown();
}

执行结果:

子线程执行任务:pool-1-thread-1
main:执行第二步处理:
main------主线程先去做其他事情
Exception in thread "main" java.util.concurrent.ExecutionException: java.lang.NullPointerException
	at java.util.concurrent.CompletableFuture.reportGet(CompletableFuture.java:357)
	at java.util.concurrent.CompletableFuture.get(CompletableFuture.java:1895)
	at com.avgrado.demo.thread.CompleteFutureDemo.main(CompleteFutureDemo.java:23)
Caused by: java.lang.NullPointerException
	at com.avgrado.demo.thread.CompleteFutureDemo.lambda$main$1(CompleteFutureDemo.java:20)
	at java.util.concurrent.CompletableFuture.uniHandle(CompletableFuture.java:822)
	at java.util.concurrent.CompletableFuture$UniHandle.tryFire(CompletableFuture.java:797)
	at java.util.concurrent.CompletableFuture.uniHandleStage(CompletableFuture.java:837)
	at java.util.concurrent.CompletableFuture.handle(CompletableFuture.java:2155)
	at com.avgrado.demo.thread.CompleteFutureDemo.main(CompleteFutureDemo.java:18)

对计算结果进行消费(thenAccept):

消费处理结果,接受任务的处理结果,并消费处理,无返回结果


private static  int calcuResult = 0;

public static void main(String[] args) throws ExecutionException, InterruptedException {
    ThreadPoolExecutor executor = new ThreadPoolExecutor(3,5,2,TimeUnit.SECONDS,new ArrayBlockingQueue<>(5));
    CompletableFuture<Void> completableFuture = CompletableFuture.supplyAsync(()->{
        try {
            System.out.println("子线程:"+Thread.currentThread().getName()+" 启动干活");
            TimeUnit.SECONDS.sleep(1);
            System.out.println("子线程:"+Thread.currentThread().getName()+" 干活结束");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        calcuResult = 100;
       return calcuResult;
    },executor).thenAccept(t -> {
        System.out.println("消费之前的结果是:"+calcuResult);
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("执行消费线程是:"+Thread.currentThread().getName());
        calcuResult --;
        System.out.println("消费之后的结果是:"+calcuResult);
    });
    executor.shutdown();

}

执行结果:

子线程:pool-1-thread-1 启动干活
子线程:pool-1-thread-1 干活结束
消费之前的结果是:100
执行消费线程是:pool-1-thread-1
消费之后的结果是:99

异常处理 (exceptionally)

在任务执行时出现异常触发

public static void main(String[] args) throws ExecutionException, InterruptedException {
    ThreadPoolExecutor executor = new ThreadPoolExecutor(3,5,2,TimeUnit.SECONDS,new ArrayBlockingQueue<>(5));
    CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(()->{
        int num  = 1/0;
        System.out.println("执行其他任务开始");
        return 0;
    },executor).exceptionally(e ->{
        System.out.println("获取到的异常信息是:"+e.getMessage());
        return -1;
    });
    executor.shutdown();

}

执行结果:

获取到的异常信息是:java.lang.ArithmeticException: / by zero
结果合并(thenCompose、thenCombine、allOf、anyOf)

thenCompose:合并两个有依赖关系的 CompletableFutures 的执行结果

private static int num = 0;
public static void main(String[] args) throws ExecutionException, InterruptedException {
    ThreadPoolExecutor executor = new ThreadPoolExecutor(3,5,2,TimeUnit.SECONDS,new ArrayBlockingQueue<>(5));
    CompletableFuture<Integer> f1 = CompletableFuture.supplyAsync(()->{
        try {
            System.out.println("子线程执行任务开始"+Thread.currentThread().getName());
            num ++;
            TimeUnit.SECONDS.sleep(2);//这里模拟第一个任务执行需要耗时
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return num;
    },executor);
    CompletableFuture<Integer> f2 = f1.thenCompose(i->
            CompletableFuture.supplyAsync(()->{
                System.out.println("合并线程执行任务开始"+Thread.currentThread().getName());
                return i +11;
            },executor)
    );
    
    System.out.println("第一个结果:"+f1.get());
    System.out.println("两个合并的结果:"+f2.get());
    executor.shutdown();
}

执行结果:

子线程执行任务开始pool-1-thread-1
第一个结果:1

---这里会等待第一个任务执行完成后才会执行合并的任务---
.........

合并线程执行任务开始pool-1-thread-2
两个合并的结果:12

thenCombine:合并两个没有依赖关系的 CompletableFutures 任务

public static void main(String[] args) throws ExecutionException, InterruptedException {
    ThreadPoolExecutor executor = new ThreadPoolExecutor(3,5,2,TimeUnit.SECONDS,new ArrayBlockingQueue<>(5));
    Instant start = Instant.now();
    CompletableFuture<Integer> f1 = CompletableFuture.supplyAsync(()->{
        int num = 0;
        try {
            System.out.println("子线程执行任务开始"+Thread.currentThread().getName());
            num ++;
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return num;
    },executor);
    CompletableFuture<Integer> f2 = CompletableFuture.supplyAsync(()->{

        int num = 0;
        System.out.println("子线程执行任务开始"+Thread.currentThread().getName());
        num =num +20;
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return num;
    },executor);

    CompletableFuture<Integer> combineResult = f1.thenCombine(f2, new BiFunction<Integer, Integer, Integer>() {

        @Override
        public Integer apply(Integer a, Integer b) {
            System.out.println("合并计算:"+Thread.currentThread().getName());
            return a+b;
        }
    });
    System.out.println("第一个结果:"+f1.get());
    System.out.println("第二个结果:"+f2.get());
    Instant end = Instant.now();
    System.out.println("任务执行耗时:"+Duration.between(start,end).toMillis());
    System.out.println("两个合并的结果:"+combineResult.get());
    executor.shutdown();
}

任务执行结果:

子线程执行任务开始pool-1-thread-1
子线程执行任务开始pool-1-thread-2
第一个结果:1
第二个结果:20
合并计算:pool-1-thread-2
2100
两个合并的结果:21

allOf: 一系列独立的 future 任务,等其所有的任务执行完后做一些事情

public static void main(String[] args) throws ExecutionException, InterruptedException {
    System.out.println("主线程开始");
    List<CompletableFuture> list = new ArrayList<>();
    CompletableFuture<Integer> job1 = CompletableFuture.supplyAsync(() -> {

        try{
            int num =10;
            Thread.sleep(5000);
            System.out.println(Thread.currentThread().getName()+":加 10 任务开始");
            num += 10;
            return num;
        }catch (Exception e){
            return 0;
        }
    });
    list.add(job1);
    CompletableFuture<Integer> job2 = CompletableFuture.supplyAsync(() -> {
        try{
            int num =10;
            Thread.sleep(2000);
            System.out.println(Thread.currentThread().getName()+":乘以 10 任务开始");
            num = num * 10;
            return num;
        }catch (Exception e){
            return 1;
        }
    });
    list.add(job2);
    CompletableFuture<Integer> job3 = CompletableFuture.supplyAsync(() -> {
        try{
            int num =10;
            Thread.sleep(3000);
            System.out.println(Thread.currentThread().getName()+":减以 10 任务开始");
            num = num * 10;
            return num;
        }catch (Exception e){
            return 2;
        }
    });
    list.add(job3);
    CompletableFuture<Integer> job4 = CompletableFuture.supplyAsync(() -> {
        try{
            int num =10;
            Thread.sleep(4000);
            System.out.println(Thread.currentThread().getName()+":除以 10 任务开始");num = num * 10;
            return num;
        }catch (Exception e){
            return 3;
        }
    });
    list.add(job4);
    //多任务合并后输出执行的结果
    List<Integer> collect  = list.stream().map(CompletableFuture<Integer>::join).collect(Collectors.toList());
    System.out.println(collect);
    
    //直接使用get获取是获取不到结果的
    CompletableFuture<Void> f = CompletableFuture.allOf(job1, job2, job3, job4);
    System.out.println("直接使用get获取结果:"+f.get());

    

执行结果:

===========这里没有使用自定义的线程池,所以使用默认的ForkJoinPool线程池================

主线程开始
ForkJoinPool.commonPool-worker-2:乘以 10 任务开始
ForkJoinPool.commonPool-worker-3:减以 10 任务开始
ForkJoinPool.commonPool-worker-4:除以 10 任务开始
ForkJoinPool.commonPool-worker-1:加 10 任务开始
[20, 100, 100, 100]

直接使用get获取结果:null

anyOf:只要在多个 future 里面有一个返回,整个任务就可以结束,而不需要等到每一个
future 结束

public static void main(String[] args) throws ExecutionException, InterruptedException {
    System.out.println("主线程开始");
    CompletableFuture<Integer> [] futures = new CompletableFuture[4];
    CompletableFuture<Integer> job1 = CompletableFuture.supplyAsync(() -> {

        try{
            int num =10;
            Thread.sleep(5000);
            System.out.println(Thread.currentThread().getName()+":加 10 任务开始");
            num += 10;
            return num;
        }catch (Exception e){
            return 0;
        }
    });
    futures[0]= job1;
    CompletableFuture<Integer> job2 = CompletableFuture.supplyAsync(() -> {
        try{
            int num =10;
            Thread.sleep(2000);
            System.out.println(Thread.currentThread().getName()+":乘以 10 任务开始");
            num = num * 10;
            return num;
        }catch (Exception e){
            return 1;
        }
    });
    futures[1]= job2;

    CompletableFuture<Integer> job3 = CompletableFuture.supplyAsync(() -> {
        try{
            int num =10;
            Thread.sleep(3000);
            System.out.println(Thread.currentThread().getName()+":减以 10 任务开始");
            num = num * 10;
            return num;
        }catch (Exception e){
            return 2;
        }
    });
    futures[2]= job3;

    CompletableFuture<Integer> job4 = CompletableFuture.supplyAsync(() -> {
        try{
            int num =10;
            Thread.sleep(4000);
            System.out.println(Thread.currentThread().getName()+":除以 10 任务开始");num = num * 10;
            return num;
        }catch (Exception e){
            return 3;
        }
    });
    futures[03]= job4;
    CompletableFuture<Object> future = CompletableFuture.anyOf(futures);
    System.out.println("直接使用get获取结果:"+future.get());

执行结果:

主线程开始
ForkJoinPool.commonPool-worker-2:乘以 10 任务开始
直接使用get获取结果:100
完成时触发(whenComplete)
private static int num = 10;
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(3,5,1,TimeUnit.SECONDS,new ArrayBlockingQueue<>(3));
        CompletableFuture<Integer> future = CompletableFuture.supplyAsync(()->{

            try {
                num++;
                System.out.println("任务执行开始:"+Thread.currentThread().getName());
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return num;
        },executor).whenComplete((v,e)->{
            System.out.println("触发完成时事件");
            if (e == null) {
                System.out.println("计算完成 更新系统" + v);
            }
            //此处增加一行触发异常代码
            int i = 1/0;
        }).exceptionally(e -> {
            e.printStackTrace();
            System.out.println("异常情况:" + e.getCause() + " " + e.getMessage());
            return null;
            }
        );
        executor.shutdown();
}

执行结果:

任务执行开始:pool-1-thread-1
触发完成时事件
计算完成 更新系统11
java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
	at java.util.concurrent.CompletableFuture.encodeThrowable(CompletableFuture.java:292)
	at java.util.concurrent.CompletableFuture.completeThrowable(CompletableFuture.java:308)
	at java.util.concurrent.CompletableFuture.uniWhenComplete(CompletableFuture.java:769)
	at java.util.concurrent.CompletableFuture$UniWhenComplete.tryFire(CompletableFuture.java:736)
	at java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:474)
	at java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1595)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)
Caused by: java.lang.ArithmeticException: / by zero
	at com.avgrado.demo.thread.CompleteFutureDemo.lambda$main$1(CompleteFutureDemo.java:40)
	at java.util.concurrent.CompletableFuture.uniWhenComplete(CompletableFuture.java:760)
	... 6 more
异常情况:java.lang.ArithmeticException: / by zero java.lang.ArithmeticException: / by zero