CompletableFuture详解、什么是CompletableFuture类,怎么在java并发编程、多线程中使用(结合案例,保姆级教程))

一、CompletableFuture类

同传统的 Future 相比较:

  • CompletableFuture 能够主动设置计算的结果值、主动终结计算过程,从而在某些场景下主动结束阻塞等待。
  • 而 Future 由于不能主动设置计算结果值,一旦调用 get() 方法进行阻塞等待,要么当计算结果产生,要么超时,才会返回。

先看个简单的示例:CompletableFuture 是如何主动完成、被动完成。

public class Test {
    public static void main(String[] args) {
        CompletableFuture<String> future = CompletableFuture.supplyAsync(()->{
            try{
                Thread.sleep(1000L); //模拟耗时任务
                return "test";
            } catch (Exception e){
                return "failed test";
            }
        });
        //情况1:
        System.out.println(future.join());
        future.complete("manual test");
        //情况2:
        future.complete("manual test");
        System.out.println(future.join());
    }
}

complete() 方法会尝试完成未完成的 CompletableFuture,如果异步任务已经完成,complete()方法则不会有影响。

① 情况1:

  • future.join()join() 方法会等待异步任务完成并获取结果。由于异步任务执行需要 1 秒时间,因此主线程会阻塞 1 秒,等待任务返回 "test",此时输出 test
  • future.complete("manual test")complete("manual test") 尝试手动完成 future,但由于任务已经完成,所以 complete() 不会有任何效果。

② 情况2:

  • future.complete("manual test"):在调用 join() 之前先调用了 complete("manual test")。此时,异步任务可能还没有完成,因为主线程立即执行了 complete(),而异步任务还在等待 1 秒。由于任务还未完成,complete() 会手动完成 CompletableFuture,并设置其结果为 "manual test",此时输出 manual test

  • future.join():当调用 join() 时,由于 complete() 已经手动完成了 CompletableFuture,因此 join() 会立即返回 "manual test",而不会等待异步任务。异步任务的结果将被忽略,因为 complete() 先行完成了 CompletableFuture

二、创建 CompletableFuture 对象

1、构造器创建

CompletableFuture<String> future = new CompletableFuture();
String result = future.join();
System.out.println(result);

当前线程会一直阻塞在这里。此时,如果在另外一个线程中,主动设置该 CompletableFuture 的值(比如:future.complete("test");,则上面线程中的结果就能返回 test。

2、supplyAsync() 方法创建

public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
 
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)

第一种:只需传入一个 Supplier 实例(一般使用 lamda 表达式),此时框架会默认使用 ForkJoin 的线程池来执行被提交的任务。

第二种:可以指定自定义的线程池,然后将任务提交给该线程池执行。

CompletableFuture<String> future = CompletableFuture.supplyAsync(()->{
    System.out.println("compute test");
    return "test";
});

String result = future.join();
System.out.println("get result: " + result);
//输出:get result: test

3、runAsync() 方法创建

与 supplyAsync() 不同的是,runAsync() 传入的任务要求是 Runnable 类型的,所以没有返回值。因此,runAsync 适合创建不需要返回值的计算任务。同 supplyAsync() 类似,runAsync() 的方式也有两种。

CompletableFuture<Void> future = CompletableFuture.runAsync(()->{
    System.out.println("compute test");
});
System.out.println("get result: " + future.join());
//输出:由于任务没有返回值, 所以最后的打印结果是 "get result: null"

三、组合多个异步任务

1、allOf() 方法:等待多个 CompletableFuture 全部完成后再执行某些操作,它返回一个新的 CompletableFuture<Void>,表示当所有提供的 CompletableFuture 都完成时,这个新的 CompletableFuture 也会完成。

public class Test {
    public static void main(String[] args) {
        CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Task 1");
        CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "Task 2");

        // 使用 allOf 组合多个异步任务
        CompletableFuture<Void> allOfFuture = CompletableFuture.allOf(future1, future2);

        // 阻塞,直到所有异步任务完成
        allOfFuture.join();

        // 输出结果
        System.out.println(future1.join());  // 输出: Task 1
        System.out.println(future2.join());  // 输出: Task 2
    }
}

为什么还需要:allOfFuture.join(); 阻塞?

CompletableFuture.allOf() 本身不会主动等待任务完成(也就是并不会阻塞主线程)。你必须显式地调用它的 等待方法,比如 join()get(),来等待所有任务的完成,否则主线程可能会在异步任务尚未完成之前继续执行。

② 为什么上面案例不需要 allOfFuture.join(); 也可以?因为主线程没有任务输出,唯一的输出就是 System.out.println(future1.join()); 但是其中 future1.join() 会阻塞等待任务完成(future1.get()效果也是一样的) ,所以要不要含义是一样的。

还没理解?再来看个例子:

public class Test {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "Task 1";
        });
        CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "Task 2");

        CompletableFuture<Void> allOfFuture = CompletableFuture.allOf(future1, future2);

        System.out.println("所有任务开始执行,但未必都已经完成");

        // 主线程会被 allOfFuture.join() 阻塞,直到 future1 和 future2 完成
        allOfFuture.join();  // 这一行确保等待所有任务完成

        try {
            // 现在可以安全地获取异步任务的结果
            System.out.println(future1.get());  
            System.out.println(future2.get());  
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("所有任务都完成,继续执行主线程的其他逻辑");
    }
}

输出:


注意: 还有个用法

CompletableFuture.allOf(future1, future2,.....).get(20, TimeUnit.SECONDS);

get 方法会阻塞当前线程,直到所有传入的 CompletableFuture 都完成,或者指定的超时时间(这里是20秒)到达。如果超时,它会抛出 TimeoutException。为什么这么做?----> 就是不要一直阻塞等待,因为上面这些任务可能执行时间长(比如:需要远程调用),假如在高并发的情况下,一直阻塞等待的话,则线程数会非常多,容易造成内存溢出。

2、anyOf():只要其中一个 CompletableFuture 完成,就返回结果。

public class Test {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "Task 1";
        });
        CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "Task 2");

        CompletableFuture<Object> anyOfFuture = CompletableFuture.anyOf(future1, future2);

        System.out.println("所有任务开始执行,但未必都已经完成");

        // 主线程会被 anyOfFuture.join() 阻塞,直到至少有一个完成
        System.out.println(anyOfFuture.join());
        
        System.out.println("所有任务都完成,继续执行主线程的其他逻辑");

        try {
            // 现在可以安全地获取异步任务的结果
            System.out.println(future1.get());  
            System.out.println(future2.get());  
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("所有任务都完成,继续执行主线程的其他逻辑");
    }
}

输出:

四、链式异步编程

CompletableFuture 支持链式调用,多个任务之间,可以前后相连,从而形成一个计算流。比如:任务1产生的结果,可以直接作为任务2的入参,参与任务2的计算,以此类推。常见的链式操作包括:

thenApply/thenApplyAsync、thenAccept/thenAcceptAsync、thenRun/thenRunAsync
thenCombine/thenCombineAsync、thenCompose/thenComposeAsync

带 Async 用法一样,但是参数多传一个,表示指定任务使用的具体线程池。

  • thenApply():用于对异步计算的结果进行后续处理。

    CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
        return "Hello";
    }).thenApply(result -> {
        return result + " World";	
    });
    
    System.out.println(future.join());  // 输出: Hello World
    

    再比如:有任务A,还有任务B。任务B需要在任务A执行完毕后再执行。而且任务B需要任务A的返回结果。任务B自身也有返回结果。

    public static void main(String[] args) {
        CompletableFuture<String> taskA = CompletableFuture.supplyAsync(() -> {
            String id = UUID.randomUUID().toString();
            System.out.println("执行任务A:" + id);
            return id;
        });
        CompletableFuture<String> taskB = taskA.thenApply(result -> {
            System.out.println("任务B获取到任务A结果:" + result);
            result = result.replace("-", "");
            return result;
        });
    
        System.out.println("main线程拿到结果:" + taskB.join());
    }
    
  • thenAccept():是一个消费者方法,它接收任务的结果并进行处理,没有返回值

    CompletableFuture.supplyAsync(() -> {
        return "Hello";
    }).thenAccept(result -> {
        System.out.println("Result: " + result);  // 输出: Result: Hello
    });
    
  • thenRun():前置任务没有返回结果,后置任务不接收前置任务结果,后置任务也没有返回结果

    public static void main(String[] args) throws IOException {
        CompletableFuture.runAsync(() -> {
            System.out.println("任务A!!");
        }).thenRun(() -> {
            System.out.println("任务B!");
        });
    }
    

注意:通过 thenApply / thenAccept / thenRun 连接的任务,当且仅当前置任务计算完成时,才会开始后置任务的计算。因此,这组函数主要用于连接前后有依赖的任务链。


  • thenCombine():用于并行执行两个异步任务,并将它们的结果合并,什么意思?

    也就是:同前面一组连接函数相比,thenCombine 最大的不同是连接任务可以是独立的,从而允许前后连接的两个任务可以并行执行(后置任务不需要等待前置任务执行完成),最后当两个任务均完成时,再将其结果同时传递给下游处理任务,从而得到最终结果。

    CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Task 1");
    CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "Task 2");
    
    CompletableFuture<String> combinedFuture = future1.thenCombine(future2, (result1, result2) -> {
        return result1 + " + " + result2;
    });
    
    System.out.println(combinedFuture.join());  // 输出: Task 1 + Task 2
    

    注意:一般,在连接任务之间互相不依赖的情况下,可以使用 thenCombine 来连接任务,从而提升任务之间的并发度。


  • thenCompose()

    前面讲了 thenCombine 主要用于没有前后依赖关系之间的任务进行连接。那么,如果两个任务之间有前后依赖关系,但是连接任务又是独立的 CompletableFuture,该怎么实现呢?

    先来看一下直接使用 thenApply 来实现:

    CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(()->{
        System.out.println("compute 1");
        return 1;
    });
    CompletableFuture<CompletableFuture<Integer>> future2 =
            future1.thenApply(r -> CompletableFuture.supplyAsync(() -> r + 10));
    
    System.out.println(future2.join().join());
    

    可以发现,上面示例代码中,future2 的类型变成了 CompletableFuture 嵌套,而且在获取结果的时候,也需要嵌套调用 join 或者 get。这样,当连接的任务越多时,代码会变得越来越复杂,嵌套获取层级也越来越深。因此,需要一种方式,能将这种嵌套模式展开,使其没有那么多层级。

    看一下通过 thenCompose 如何实现上面的代码:

    CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(()->{
        System.out.println("compute 1");
        return 1;
    });
    CompletableFuture<Integer> future2 = future1.thenCompose(r -> CompletableFuture.supplyAsync(() -> r + 10));
    
    System.out.println(future2.join());
    

    也就是允许你将多个 CompletableFuture 串联起来,第二个任务依赖于第一个任务的结果。

    //这里再以链式调用的方式举个例子:
    CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
        return "Task 1";
    }).thenCompose(result -> CompletableFuture.supplyAsync(() -> {
        return result + " + Task 2";
    }));
    
    System.out.println(future.join());  // 输出: Task 1 + Task 2
    

五、异常处理

CompletableFuture 提供了对异常的处理机制,类似于 try-catch:

  • exceptionally():只有前面业务执行时出现异常了,才会执行当前方法来处理。注意:只有异常出现时、任务没有处理完时,才会触发。

    CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
        if (true) {
            throw new RuntimeException("Something went wrong");
        }
        return "Success";
    }).exceptionally(ex -> {
        return "exception: " + ex.getMessage();
    });
    
    System.out.println(future.join());  // 输出: exception: Something went wrong
    
  • whenComplete():可以拿到返回结果,同时也可以拿到出现的异常信息,但不能返回结果(也就是没有return)

    public class Test {
        public static void main(String[] args) {
            CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(()->{
                System.out.println("compute 1");
                return 1;
            });
            //也可以以链式调用的形式写
            //r:前置任务的处理结果  e:前置任务的异常,没有为null
            CompletableFuture future2 = future1.whenComplete((r, e)->{
                if(e != null){
                    System.out.println("compute failed!");
                    //不能有return语句
                } else {
                    System.out.println("received result is " + r);
                }
            });
            System.out.println("result: " + future2.join());
        }
    }
    

    输出:需要注意的是,future2 获得的结果是前置任务的结果,whenComplete 中的逻辑不会影响计算结果。

  • handle():与 whenComplete 的作用有些类似,但是 handle 有返回值,而且返回值会影响最终获取的计算结果。

    public class Test {
     	public static void main(String[] args) {
            CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> {
                System.out.println("compute 1");
                return 1;
            });
            CompletableFuture<Integer> future2 = future1.handle((r, e) -> {
                if (e != null) {
                    System.out.println("compute failed!");
                    //有返回语句
                    return r;
                } else {
                    System.out.println("received result is " + r);
                    return r + 10;
                }
            });
            System.out.println("result: " + future2.join());
        }
    }
    

    输出:
    在这里插入图片描述

创作不易,谢谢你的赞和评论!!!精彩不断!!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小学鸡!

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值