【Java并发】CompletableFuture详解:常用API和底层原理

【Java并发】CompletableFuture详解:常用API和底层原理

  • 1、为什么要引入Future和CompletableFuture?
    • [1.1 传统异步计算存在问题?](#1.1 传统异步计算存在问题?)
    • [1.2 Future的引入](#1.2 Future的引入)
    • [1.3 Future 的局限性](#1.3 Future 的局限性)
    • [1.4 CompletableFuture的引入](#1.4 CompletableFuture的引入)
  • [2、CompletableFuture常用 API 详解](#2、CompletableFuture常用 API 详解)
    • [2.1 创建 CompletableFuture](#2.1 创建 CompletableFuture)
    • [2.2 结果转换与消费](#2.2 结果转换与消费)
    • [2.3 组合多个 CompletableFuture](#2.3 组合多个 CompletableFuture)
    • [2.4 等待多个任务完成:allOf 与 anyOf](#2.4 等待多个任务完成:allOf 与 anyOf)
      • [2.4.1 allOf:等待所有任务完成](#2.4.1 allOf:等待所有任务完成)
      • [2.4.2 anyOf:任意一个任务完成](#2.4.2 anyOf:任意一个任务完成)
      • [2.4.3 allOf 原理与异常处理](#2.4.3 allOf 原理与异常处理)
      • [2.4.4 anyOf 原理与异常处理](#2.4.4 anyOf 原理与异常处理)
  • 3、异常处理机制
    • [3.1 异常的传播](#3.1 异常的传播)
    • [3.2 处理异常的方法](#3.2 处理异常的方法)
    • [3.3 异常处理的底层原理](#3.3 异常处理的底层原理)
  • 4、completableFuture原理解析
    • [4.1 类继承关系](#4.1 类继承关系)
    • [4.2 核心字段](#4.2 核心字段)
    • [4.3 核心方法源码解析](#4.3 核心方法源码解析)
      • [4.3.1 创建与完成](#4.3.1 创建与完成)
      • [4.3.2 thenApply 原理](#4.3.2 thenApply 原理)
        • [4.3.2.1 case1--不同future执行thenApply方法](#4.3.2.1 case1--不同future执行thenApply方法)
        • [4.3.2.2 case2--同一个future执行thenApply方法](#4.3.2.2 case2--同一个future执行thenApply方法)
      • [4.3.3 组合方法:thenCombine 原理](#4.3.3 组合方法:thenCombine 原理)
      • [4.4.4 与核心 API 的对比理解](#4.4.4 与核心 API 的对比理解)

1、为什么要引入Future和CompletableFuture?

1.1 传统异步计算存在问题?

传统异步计算有一个核心问题是:无法获取异步任务的返回值。

java 复制代码
public class TraditionalAsyncDemo {

    // 模拟一个耗时操作
    public static String fetchUserInfo() {
        try {
            Thread.sleep(2000); // 模拟网络请求
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "UserInfo: {name='张三', age=25}";
    }

    // 传统方式:使用Thread + 回调
    public static void traditionalWay() {
        System.out.println("开始获取用户信息...");

        // 启动新线程执行耗时任务
        new Thread(() -> {
            String result = fetchUserInfo();
            System.out.println("获取结果: " + result);
        }).start();

        System.out.println("主线程继续执行其他任务...");
    }

    public static void main(String[] args) {
        traditionalWay();
        // 主线程不能等待结果,也无法获取返回值
    }
}

运行结果:

java 复制代码
开始获取用户信息...
主线程继续执行其他任务...
获取结果: UserInfo: {name='张三', age=25}

1.2 Future的引入

Future 接口提供了异步计算结果的占位符,允许我们在提交任务后继续执行其他操作,稍后再获取结果:

java 复制代码
import java.util.concurrent.*;

public class FutureDemo {
    
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executor = Executors.newFixedThreadPool(2);
        
        System.out.println("开始执行异步任务...");
        
        // 提交异步任务,返回Future
        Future<String> future = executor.submit(() -> {
            Thread.sleep(2000); // 模拟耗时操作
            return "Task result: Hello Future!";
        });
        
        System.out.println("主线程继续执行其他操作...");
        
        // 其他操作...
        Thread.sleep(1000);
        
        // 获取异步结果(会阻塞直到结果可用)
        System.out.println("开始获取结果...");
        String result = future.get(); // 阻塞等待
        System.out.println("获取到结果: " + result);
        
        executor.shutdown();
    }
}

运行结果:

java 复制代码
开始执行异步任务...
主线程继续执行其他操作...
开始获取结果...
获取到结果: Task result: Hello Future!

1.3 Future 的局限性

尽管 Future 解决了返回值问题,但它仍然存在很多局限:

1、无法手动完成计算:不能主动设置结果。

2、不支持链式调用:无法将结果直接传递给下一个任务。

3、无法组合多个 Future:不能优雅地合并异步任务。

4、获取结果时必须阻塞:get() 方法会阻塞主线程。

5、异常处理复杂:需要先捕获 ExecutionException 才能拿到原始异常。

java 复制代码
import java.util.concurrent.*;

public class FutureLimitationsDemo {
    public static void main(String[] args) throws Exception {
        ExecutorService executor = Executors.newFixedThreadPool(3);
        
        // 局限性1:无法手动完成
        Future<String> future = executor.submit(() -> "task");
        // future.complete(...); // 不存在这样的方法!无法提前设置结果
        
        // 局限性2:无法链式调用
        Future<String> f1 = executor.submit(() -> "hello");
        // 无法将结果传递给下一个任务,比如 f1 -> toUpperCase -> 输出
        
        // 局限性3:无法组合多个Future
        Future<String> f2 = executor.submit(() -> "World");
        // 无法优雅地组合 f1 和 f2 的结果
        
        // 局限性4:获取结果时会阻塞
        Future<String> blocking = executor.submit(() -> {
            Thread.sleep(2000);
            return "result";
        });
        String result = blocking.get(); // 必须阻塞等待,无法设置超时或不等待
        
        // 局限性5:异常处理复杂
        Future<String> errorFuture = executor.submit(() -> {
            throw new RuntimeException("Task failed!");
        });
        try {
            errorFuture.get();
        } catch (ExecutionException e) {
            // 需要捕获 ExecutionException 才能拿到原始异常
            System.out.println("原始异常:" + e.getCause().getMessage());
        }
        
        executor.shutdown();
    }
}

运行结果:

java 复制代码
原始异常:Task failed!

1.4 CompletableFuture的引入

下面的代码完整演示了 CompletableFuture 如何解决 Future 的 5 大局限:

1、无法手动完成计算:可以使用complete方法手动设置结果。

2、不支持链式调用:可以使用thenApply、thenAccept、thenRun方法进行链式调用。

3、无法组合多个 Future:可以使用thenCombine等方法优雅地合并异步任务。

4、获取结果时必须阻塞:可以使用getNow方法立刻获取值,或者使用get(1, TimeUnit.SECONDS)设置超时时间。

5、异常处理复杂:可以使用exceptionally(ex -> {})、handle()方法处理异常情况。不需要先捕获 ExecutionException 才能拿到原始异常。

java 复制代码
import java.util.concurrent.*;
import java.util.function.*;

public class CompletableFutureVsFutureDemo {

    public static void main(String[] args) throws Exception {

        // ========== 局限性1:无法手动完成 ==========
        // Future 没有 complete() 方法,只能被动等待任务完成
        // CompletableFuture 可以手动设置结果
        CompletableFuture<String> manualFuture = new CompletableFuture<>();
        // 模拟其他线程异步设置结果
        new Thread(() -> {
            try {
                Thread.sleep(1000);
                manualFuture.complete("手动设置的结果");
                // manualFuture.completeExceptionally(new RuntimeException("手动异常"));
            } catch (InterruptedException e) {
                manualFuture.completeExceptionally(e);
            }
        }).start();
        System.out.println("解决局限性1---手动完成结果: " + manualFuture.get()); // 输出: 手动设置的结果

        // ========== 局限性2:无法链式调用 ==========
        // Future 不能将计算结果直接传递给下一个任务
        // CompletableFuture 支持 thenApply、thenAccept、thenRun 等链式操作
        CompletableFuture.supplyAsync(() -> "hello")
                .thenApply(s -> s.toUpperCase())          // 转换结果
                .thenApply(s -> s + " WORLD")             // 再次转换
                .thenAccept(System.out::println)          // 消费结果,无返回值
                .thenRun(() -> System.out.println("\n解决局限性2---链式调用完成")); // 最终执行
        // 输出: HELLO WORLD\n链式调用完成

        // ========== 局限性3:无法组合多个Future ==========
        // Future 无法优雅地组合多个异步任务的结果
        // CompletableFuture 提供 thenCombine、allOf、anyOf 等组合方法
        CompletableFuture<String> f1 = CompletableFuture.supplyAsync(() -> "Hello");
        CompletableFuture<String> f2 = CompletableFuture.supplyAsync(() -> "World");
        CompletableFuture<String> combined = f1.thenCombine(f2, (r1, r2) -> r1 + " " + r2);
        System.out.println("\n解决局限性3---组合结果是: " + combined.get()); // 输出: Hello World

        // ========== 局限性4:获取结果时会阻塞 ==========
        // Future.get() 是阻塞的,无法设置超时或非阻塞获取
        // CompletableFuture 提供非阻塞获取方法 getNow()
        CompletableFuture<String> blockingFuture = CompletableFuture.supplyAsync(() -> {
            try { Thread.sleep(2000); } catch (InterruptedException e) {}
            return "延迟结果";
        });
        // 非阻塞获取:若未完成则返回默认值
        String now = blockingFuture.getNow("默认值");
        System.out.println("\n解决局限性4---非阻塞获取: " + now); // 大概率输出: 默认值

        // 也可以设置超时,超时未完成则抛出异常
        try {
            blockingFuture.get(1, TimeUnit.SECONDS);
        } catch (TimeoutException e) {
            System.out.println("超时了,还没拿到结果");
        }

        // ========== 局限性5:异常处理复杂 ==========
        // Future 需要捕获 ExecutionException 才能拿到原始异常
        // CompletableFuture 提供 exceptionally、handle 等便捷方法
        CompletableFuture<String> errorFuture = CompletableFuture.<String>supplyAsync(() -> {
            throw new RuntimeException("原始异常");
        }).exceptionally(ex -> {
            System.out.println("优雅处理异常: " + ex.getMessage());
            return "降级结果";
        });
        System.out.println("\n解决局限性5---异常处理后的结果: " + errorFuture.get()); // 输出: 降级结果

        Thread.sleep(2000); // 等待所有异步任务结束
    }
}

运行结果:

java 复制代码
解决局限性1---手动完成结果: 手动设置的结果
HELLO WORLD

解决局限性2---链式调用完成

解决局限性3---组合结果是: Hello World

解决局限性4---非阻塞获取: 默认值
超时了,还没拿到结果
优雅处理异常: java.lang.RuntimeException: 原始异常

解决局限性5---异常处理后的结果: 降级结果

2、CompletableFuture常用 API 详解

2.1 创建 CompletableFuture

1、直接new CompletableFuture<>();

2、runAsync:无返回值

3、supplyAsync:有返回值

4、指定线程池

java 复制代码
import java.util.concurrent.*;

public class CreateDemo {
    public static void main(String[] args) throws Exception {
        // 1. 直接 new(手动完成)
        CompletableFuture<String> future = new CompletableFuture<>();
        future.complete("手动完成");
        System.out.println(future.get()); // 输出: 手动完成

        // 2. runAsync:无返回值
        CompletableFuture<Void> runFuture = CompletableFuture.runAsync(() -> {
            System.out.println("runAsync 执行,无返回值");
        });
        runFuture.get(); // 等待完成

        // 3. supplyAsync:有返回值
        CompletableFuture<String> supplyFuture = CompletableFuture.supplyAsync(() -> {
            return "supplyAsync 执行,有返回值";
        });
        System.out.println(supplyFuture.get());

        // 4. 指定线程池
        ExecutorService executor = Executors.newFixedThreadPool(2);
        CompletableFuture<String> customFuture = CompletableFuture.supplyAsync(() -> {
            return "自定义线程池: " + Thread.currentThread().getName();
        }, executor);
        System.out.println(customFuture.get());
        executor.shutdown();
    }
}

运行结果:

java 复制代码
手动完成
runAsync 执行,无返回值
supplyAsync 执行,有返回值
自定义线程池: pool-1-thread-1

2.2 结果转换与消费

一、根据入参和出参来分类:

1、thenApply(Function):有入参、有返回值。

2、thenAccept(Consumer):有入参,无返回值。

3、thenRun(Runnable):无入参,无返回值。

二、根据与上游是否共用线程来区分

1、thenApply(Function):同步执行转换,与上游共用线程(若上游已完成,则在当前线程执行)。

2、thenApplyAsync(Function, Executor):异步执行转换,使用自定义线程池,不使用上游的线程。

三、根据返回的CompletableFuture是否扁平化来区分

1、thenApply:返回的是非嵌套的future,CompletableFuture<CompletableFuture>

2、thenCompose:返回的是扁平化的future,CompletableFuture

java 复制代码
import java.util.concurrent.*;

public class TransformDemo {
    public static void main(String[] args) throws Exception {
        // 有入参有返回值:将结果转为大写,再拼接后缀
        CompletableFuture<String> f1 = CompletableFuture.supplyAsync(() -> "hello")
                .thenApply(s -> s.toUpperCase())          // String -> String
                .thenApply(s -> s + " WORLD");            // 链式调用,继续转换
        System.out.println("thenApply--有入参、有返回值: " + f1.get()); // HELLO WORLD

        // 异步转换(切换线程)
        CompletableFuture<String> f2 = CompletableFuture.supplyAsync(() -> "hello")
                .thenApplyAsync(s -> {
                    System.out.println("    异步转换线程: " + Thread.currentThread().getName());
                    return s.toUpperCase();
                });
        System.out.println("    thenApplyAsync: " + f2.get()); // HELLO

        // 有入参无返回值:消费结果并打印
        CompletableFuture<Void> f3 = CompletableFuture.supplyAsync(() -> "hello")
                .thenAccept(s -> System.out.println("thenAccept--有入参、无返回值---消费结果: " + s));
        f3.get(); // 等待消费完成

        // 无入参无返回值:上游完成后执行一段逻辑
        CompletableFuture<Void> f4 = CompletableFuture.supplyAsync(() -> "hello")
                .thenRun(() -> System.out.println("thenRun--无入参、无返回值--上游任务执行完毕"));
        f4.get();

        // 嵌套情况(不推荐)
        System.out.println("\n");
        CompletableFuture<CompletableFuture<String>> nestedFuture =
                CompletableFuture.supplyAsync(() -> "hello")
                        .thenApply(s -> CompletableFuture.supplyAsync(() -> s + " world"));

        // 使用 thenCompose 扁平化
        CompletableFuture<String> flatFuture = CompletableFuture.supplyAsync(() -> "hello")
                .thenCompose(s -> CompletableFuture.supplyAsync(() -> s + " world"));
        System.out.println("thenCompose: " + flatFuture.get()); // hello world
    }
}

运行结果:

java 复制代码
thenApply--有入参、有返回值: HELLO WORLD
    异步转换线程: ForkJoinPool.commonPool-worker-1
    thenApplyAsync: HELLO
thenAccept--有入参、无返回值---消费结果: hello
thenRun--无入参、无返回值--上游任务执行完毕

thenCompose: hello world

2.3 组合多个 CompletableFuture

1、thenCombine:合并2个future的结果,可以有返回值。

2、thenAcceptBoth:合并2个future的结果,无返回值。

3、applyToEither:取先完成的结果

java 复制代码
import java.util.concurrent.*;

public class CombineDemo {
    public static void main(String[] args) throws Exception {
        // 1. thenCombine:合并两个独立结果
        CompletableFuture<String> f1 = CompletableFuture.supplyAsync(() -> "Hello");
        CompletableFuture<String> f2 = CompletableFuture.supplyAsync(() -> "World");
        CompletableFuture<String> combined = f1.thenCombine(f2, (r1, r2) -> r1 + " " + r2);
        System.out.println("thenCombine: " + combined.get());
        
        // 2. thenAcceptBoth:组合并消费
        f1.thenAcceptBoth(f2, (r1, r2) -> System.out.println("thenAcceptBoth: " + r1 + " " + r2))
          .get();
        
        // 3. applyToEither:取先完成的结果
        CompletableFuture<String> fast = CompletableFuture.supplyAsync(() -> {
            sleep(1000);
            return "Fast";
        });
        CompletableFuture<String> slow = CompletableFuture.supplyAsync(() -> {
            sleep(2000);
            return "Slow";
        });
        CompletableFuture<String> either = fast.applyToEither(slow, s -> s + " wins!");
        System.out.println("applyToEither: " + either.get());
    }
    
    static void sleep(long ms) {
        try { Thread.sleep(ms); } catch (InterruptedException e) {}
    }
}

运行结果:

java 复制代码
thenCombine: Hello World
thenAcceptBoth: Hello World
applyToEither: Fast wins!

2.4 等待多个任务完成:allOf 与 anyOf

2.4.1 allOf:等待所有任务完成

java 复制代码
import java.util.concurrent.*;
import java.util.stream.*;

public class AllOfDemo {
    public static void main(String[] args) throws Exception {
        // 创建 3 个任务
        CompletableFuture<String> task1 = CompletableFuture.supplyAsync(() -> {
            sleep(1000);
            return "Task1";
        });
        CompletableFuture<String> task2 = CompletableFuture.supplyAsync(() -> {
            sleep(2000);
            return "Task2";
        });
        CompletableFuture<String> task3 = CompletableFuture.supplyAsync(() -> {
            sleep(1500);
            return "Task3";
        });
        
        // allOf 返回 CompletableFuture<Void>,等待所有任务完成
        CompletableFuture<Void> all = CompletableFuture.allOf(task1, task2, task3);
        
        // 阻塞等待所有任务完成
        all.get();
        
        // 所有任务完成后,收集结果
        String result = Stream.of(task1, task2, task3)
            .map(CompletableFuture::join) // join 不抛出 checked exception
            .collect(Collectors.joining(", "));
        
        System.out.println("所有任务完成: " + result);
    }
    
    static void sleep(long ms) {
        try { Thread.sleep(ms); } catch (InterruptedException e) {}
    }
}

运行结果:

java 复制代码
所有任务完成: Task1, Task2, Task3

2.4.2 anyOf:任意一个任务完成

java 复制代码
import java.util.concurrent.*;

public class AnyOfDemo {
    public static void main(String[] args) throws Exception {
        CompletableFuture<String> task1 = CompletableFuture.supplyAsync(() -> {
            sleep(2000);
            return "Task1 (slow)";
        });
        CompletableFuture<String> task2 = CompletableFuture.supplyAsync(() -> {
            sleep(1000);
            return "Task2 (fast)";
        });
        
        // anyOf 返回 CompletableFuture<Object>
        CompletableFuture<Object> any = CompletableFuture.anyOf(task1, task2);
        
        // 获取最先完成的结果
        Object result = any.get();
        System.out.println("最先完成的任务: " + result);
        
        // 注意:anyOf 完成后,其他任务依然在后台继续执行
        Thread.sleep(2000); // 等待其他任务完成,避免程序退出
    }
    
    static void sleep(long ms) {
        try { Thread.sleep(ms); } catch (InterruptedException e) {}
    }
}

演示结果:

java 复制代码
最先完成的任务: Task2 (fast)

2.4.3 allOf 原理与异常处理

一、源码解析及注意事项

1、每个子任务完成(正常或异常)时,都会减少计数器。

2、如果子任务异常,会立即调用 result.completeExceptionally(t),即 result 会马上被标记为异常完成(即使还有其他子任务未完成)

3、但计数器仍然会继续递减,等待所有子任务完成(因为 whenComplete 回调始终会执行)。

4、当所有子任务都完成后,如果之前没有异常,result 被正常完成;如果已有异常,则 result 已经完成,不再重复。

总结:

1、allOf 返回的 Future 在任一子任务异常时就会立即异常完成。

2、未完成的其他子任务不会被取消,会继续执行(它们的 whenComplete 回调仍会触发,但不再影响 result 的状态)。

3、异常是第一个发生的异常(通过 ex[0] 记录)。

java 复制代码
public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs) {
    CompletableFuture<Void> result = new CompletableFuture<>();
    final AtomicInteger count = new AtomicInteger(cfs.length);
    final Throwable[] ex = new Throwable[1];  // 用于记录第一个异常

    for (CompletableFuture<?> cf : cfs) {
        cf.whenComplete((r, t) -> {
            // 1. 如果当前子任务异常,记录第一个异常并尝试异常完成 result
            if (t != null) {
                synchronized (ex) {
                    if (ex[0] == null) ex[0] = t;
                }
                result.completeExceptionally(t);
            }
            // 2. 计数减一
            if (count.decrementAndGet() == 0) {
                // 3. 当所有子任务都完成时,若没有异常则正常完成 result
                if (ex[0] == null) {
                    result.complete(null);
                }
                // 如果有异常,result 已经异常完成,这里无需再处理
            }
        });
    }
    return result;
}

二、代码演示:allOf 的异常场景

1、allOf 在 task2 异常时立即完成并抛出异常。

2、其他任务(task1 和 task3)仍然正常执行完毕。

3、单独获取每个子任务的结果时,task2 抛出异常,其余正常。

java 复制代码
import java.util.concurrent.*;

public class AllOfExceptionDemo {
    public static void main(String[] args) {
        // 创建三个任务:两个正常,一个异常
        CompletableFuture<String> task1 = CompletableFuture.supplyAsync(() -> {
            sleep(500);
            return "Task1 OK";
        });
        CompletableFuture<String> task2 = CompletableFuture.supplyAsync(() -> {
            sleep(1000);
            throw new RuntimeException("Task2 失败了!");
        });
        CompletableFuture<String> task3 = CompletableFuture.supplyAsync(() -> {
            sleep(1500);
            return "Task3 OK";
        });

        // 等待所有任务完成(allOf 会在 task2 异常时立即异常)
        CompletableFuture<Void> all = CompletableFuture.allOf(task1, task2, task3);

        try {
            all.join();  // 会抛出 CompletionException,包装 task2 的异常
        } catch (CompletionException e) {
            System.out.println("allOf 捕获异常: " + e.getCause().getMessage());
        }

        // 检查每个子任务的状态(注意 task1 和 task3 仍然会完成)
        System.out.println("\n检查各子任务结果:");
        System.out.println("task1 结果: " + task1.join());
        try {
            task2.join();
        } catch (CompletionException e) {
            System.out.println("task2 异常: " + e.getCause().getMessage());
        }
        System.out.println("task3 结果: " + task3.join());
    }

    static void sleep(long ms) {
        try { Thread.sleep(ms); } catch (InterruptedException e) {}
    }
}

运行结果:

java 复制代码
allOf 捕获异常: Task2 失败了!

检查各子任务结果:
task1 结果: Task1 OK
task2 异常: Task2 失败了!
task3 结果: Task3 OK

三、常见处理模式

java 复制代码
// 等待所有任务完成(无论是否异常),然后收集结果
CompletableFuture.allOf(task1, task2, task3)
    .exceptionally(ex -> {  // 这里捕获的是 allOf 的异常,但不会取消其他任务
        System.out.println("allOf 异常: " + ex.getMessage());
        return null;
    })
    .join();

// 单独获取每个任务的结果(处理异常)
List<String> results = Stream.of(task1, task2, task3)
    .map(f -> {
        try {
            return f.join();
        } catch (CompletionException e) {
            return "ERROR: " + e.getCause().getMessage();
        }
    })
    .collect(Collectors.toList());
System.out.println(results);

2.4.4 anyOf 原理与异常处理

一、源码解析及注意事项

1、第一个完成(无论正常还是异常)的子任务,会立即完成 result。

2、如果先完成的是正常任务,result 正常完成;如果先完成的是异常任务,result 异常完成。

3、后续完成的任务不会影响 result 的状态。

4、未完成的任务不会被取消,它们会继续执行(可能导致资源浪费)。

java 复制代码
public static CompletableFuture<Object> anyOf(CompletableFuture<?>... cfs) {
    CompletableFuture<Object> result = new CompletableFuture<>();
    for (CompletableFuture<?> cf : cfs) {
        cf.whenComplete((r, t) -> {
            // 使用 CAS 保证只完成一次(内部实现是 complete 或 completeExceptionally)
            if (t != null) {
                result.completeExceptionally(t);
            } else {
                result.complete(r);
            }
        });
    }
    return result;
}

二、代码演示:anyOf 的异常场景

1、anyOf 的结果由最先完成的子任务决定(异常时抛出)。

2、未完成的任务(slowSuccess)仍会继续执行。

java 复制代码
import java.util.concurrent.*;

public class AnyOfExceptionDemo {
    public static void main(String[] args) {
        // 创建两个任务:一个快但异常,一个慢但正常
        CompletableFuture<String> fastFail = CompletableFuture.supplyAsync(() -> {
            sleep(500);
            throw new RuntimeException("快速失败任务异常");
        });
        CompletableFuture<String> slowSuccess = CompletableFuture.supplyAsync(() -> {
            sleep(2000);
            return "慢速成功任务结果";
        });

        CompletableFuture<Object> any = CompletableFuture.anyOf(fastFail, slowSuccess);

        try {
            Object result = any.get(); // 会立即抛出异常(因为 fastFail 先完成且异常)
            System.out.println("anyOf 结果: " + result);
        } catch (ExecutionException e) {
            System.out.println("anyOf 捕获ExecutionException异常: " + e.getCause().getMessage());
        } catch (InterruptedException e) {
            System.out.println("anyOf 捕获InterruptedException异常: " + e.getCause().getMessage());
        }

        // 稍等,观察慢速任务是否仍然执行(不会取消)
        sleep(3000);
        System.out.println("慢速任务结果: " + slowSuccess.join());
    }

    static void sleep(long ms) {
        try { Thread.sleep(ms); } catch (InterruptedException e) {}
    }
}

运行结果:

java 复制代码
anyOf 捕获ExecutionException异常: 快速失败任务异常
慢速任务结果: 慢速成功任务结果

3、异常处理机制

3.1 异常的传播

在 CompletableFuture 中,异常会沿着调用链向下传播,直到被处理。

java 复制代码
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

public class ExceptionPropagationDemo {
    public static void main(String[] args) throws Exception {
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            throw new RuntimeException("原始异常");
        }).thenApply(s -> {
            System.out.println("这个不会执行");
            return s + " after";
        }).thenApply(s -> {
            System.out.println("这个也不会执行");
            return s + " again";
        });

        try {
            future.get();
        } catch (ExecutionException e) {
            System.out.println("捕获异常: " + e.getCause().getMessage());
        }
    }
}

运行结果:

java 复制代码
捕获异常: 原始异常

3.2 处理异常的方法

一、exceptionally:从异常中恢复

exceptionally 只处理异常,如果上游正常完成,它不会执行。可以在exceptionally方法中处理异常并返回默认值。

java 复制代码
import java.util.concurrent.CompletableFuture;

public class ExceptionallyDemo {
    public static void main(String[] args) throws Exception {
        CompletableFuture<String> future = CompletableFuture.<String>supplyAsync(() -> {
            throw new RuntimeException("任务失败");
        }).exceptionally(ex -> {
            System.out.println("异常处理: " + ex.getMessage());
            return "默认值";
        });

        System.out.println("结果: " + future.get());
    }
}

运行结果:

java 复制代码
异常处理: java.lang.RuntimeException: 任务失败
结果: 默认值

二、handle:处理正常和异常结果

java 复制代码
public class HandleDemo {
    public static void main(String[] args) throws Exception {
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            // 模拟正常或异常
            if (Math.random() > 0.5) {
                return "成功";
            } else {
                throw new RuntimeException("失败");
            }
        }).handle((result, ex) -> {
            if (ex != null) {
                return "异常处理: " + ex.getMessage();
            }
            return "正常结果: " + result;
        });

        System.out.println(future.get());
    }
}

运行结果:

java 复制代码
异常处理: java.lang.RuntimeException: 失败

三:whenComplete:不影响结果future的结果

java 复制代码
public class WhenCompleteDemo {
    public static void main(String[] args) throws Exception {
        CompletableFuture<String> future = CompletableFuture.<String>supplyAsync(() -> {
            throw new RuntimeException("任务失败");
        }).whenComplete((result, ex) -> {
            if (ex != null) {
                System.out.println("whenComplete方法--记录日志: " + ex.getMessage());
            } else {
                System.out.println("whenComplete方法--记录日志: 成功 " + result);
            }
        });

        try {
            future.get();
        } catch (ExecutionException e) {
            System.out.println("主流程捕获异常: " + e.getCause().getMessage());
        }
    }
}

运行结果:

java 复制代码
whenComplete方法--记录日志: java.lang.RuntimeException: 任务失败
主流程捕获异常: 任务失败

3.3 异常处理的底层原理

CompletableFuture 内部使用一个 volatile Object result 来存储结果或异常。当任务执行异常时,会调用 completeExceptionally(Throwable ex),将 ex 包装成 AltResult 对象存入 result 字段。

java 复制代码
// 异常完成的核心代码(简化)
public boolean completeExceptionally(Throwable ex) {
    // 将异常包装为 AltResult,存入 result
    return completeValue(new AltResult(ex));
}

static final class AltResult {
    final Throwable ex;
    AltResult(Throwable ex) { this.ex = ex; }
}

后续的依赖任务在触发时,会检查当前 Future 的 result 是否为 AltResult,如果是,则将异常传递下去,或调用异常处理方法。

以 thenApply 为例,内部实现会判断上游结果是否为异常。异常处理的链式传播正是通过这种机制实现的。

java 复制代码
// thenApply 内部执行逻辑(简化)
public <U> CompletableFuture<U> thenApply(Function<? super T,? extends U> fn) {
    CompletableFuture<U> d = new CompletableFuture<>();
    // 如果当前已经完成,直接执行
    Object r = result;
    if (r != null) {
        // 如果结果异常,传播异常
        if (r instanceof AltResult) {
            d.completeExceptionally(((AltResult)r).ex);
        } else {
            // 正常执行转换
            try {
                d.complete(fn.apply((T) r));
            } catch (Throwable ex) {
                d.completeExceptionally(ex);
            }
        }
    } else {
        // 否则,将依赖任务加入栈中,等待完成时触发
        push(new UniApply<>(d, fn));
    }
    return d;
}

4、completableFuture原理解析

4.1 类继承关系

CompletableFuture 实现了两个关键接口:

1、Future:提供异步任务的基本操作,如 get()、cancel()、isDone()。

2、CompletionStage:这是 Java 8 新增的接口,定义了异步任务编排的方法,如 thenApply、thenCombine、whenComplete 等。CompletableFuture 是它的核心实现。通过 CompletionStage 接口,我们才能像搭积木一样链式组合任务。

java 复制代码
public class CompletableFuture<T> implements Future<T>, CompletionStage<T> {
    // ...
}

4.2 核心字段

CompletableFuture 的核心状态只有两个 volatile 字段(源码中还有 UNSAFE 辅助偏移量,但核心是这两个):

1、result 的取值:null(未完成)、T(正常结果)、AltResult(包装的异常)。

2、stack 是一个 Completion 节点,是等待任务的栈顶,是一个单向链表。每个节点代表一个等待此 Future 完成后执行的动作(如 thenApply 创建的 UniApply 节点)

java 复制代码
public class CompletableFuture<T> {
    // 存储结果或异常。null 表示未完成,非 null 表示已完成(可能为 AltResult 包装异常)
    volatile Object result;
    
    // 等待任务的栈顶,是一个单向链表,存储所有依赖此 Future 完成的任务节点
    volatile Completion stack;
    
    // 其他辅助字段(如 UNSAFE 用于 CAS 操作)这里略去
}

Completion 是抽象类,它的子类对应各种操作,常用子类:

1、UniApply<T,V>:对应 thenApply

2、UniAccept:对应 thenAccept

3、BiApply<T,U,V>:对应 thenCombine

4、OrApply<T,U,V>:对应 applyToEither

每个子类都持有下游 CompletableFuture 和具体的函数,以及可能的上游结果。

java 复制代码
abstract static class Completion extends ForkJoinTask<Void> {
    volatile Completion next; // 链表的下一个节点
    
    // 核心执行方法,由子类实现
    abstract void run();
}

核心字段的使用方式:

  1. 状态存储:通过 volatile Object result 存储结果或异常,使用 CAS 保证原子更新。
  2. 依赖管理:每个 CompletableFuture 维护一个 stack 链表,存放所有等待它完成的任务节点
  3. 任务触发。当 complete 或 completeExceptionally 被调用时,postComplete 遍历链表并执行每个节点的 run 方法。
  4. 节点类型:每个编排方法(如 thenApply)都会生成一个对应的 Completion 子类,它持有下游 Future 和具体函数。
  5. 异常传播:若上游结果包含 AltResult,下游在 run 时会直接 completeExceptionally,异常沿链传播。
  6. 异步与同步:thenApply 与 thenApplyAsync 的区别在于:thenApply 在任务完成时由触发线程执行;thenApplyAsync 则总是提交到线程池。

4.3 核心方法源码解析

4.3.1 创建与完成

supplyAsync 简化逻辑:

1、正常完成执行complete方法,异常完成执行completeExceptionally方法。

2、异常完成会创建一个AltResult对象,并赋值给result字段。

3、result字段赋值完成后,执行postComplete方法,触发所有等待任务进行执行。即栈顶stack指向下一个Completion。

java 复制代码
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier) {
    CompletableFuture<U> f = new CompletableFuture<>();
    // 提交任务到默认线程池
    ForkJoinPool.commonPool().execute(() -> {
        try {
            U result = supplier.get();
            f.complete(result);          // 正常完成
        } catch (Throwable t) {
            f.completeExceptionally(t);  // 异常完成
        }
    });
    return f;
}

// 正常完成
public boolean complete(T value) {
    return completeValue(value);
}

// 异常完成
public boolean completeExceptionally(Throwable ex) {
    return completeValue(new AltResult(ex));
}

// 底层完成方法(简化)
final boolean completeValue(Object r) {
    // CAS 设置 result,从 null 变为 r(或 AltResult)
    if (UNSAFE.compareAndSwapObject(this, RESULT_OFFSET, null, r)) {
        // 设置成功后,触发所有等待任务
        postComplete();
        return true;
    }
    return false;
}

final void postComplete() {
    CompletableFuture<?> f = this;
    Completion h;
    // 循环弹出栈顶
    while ((h = f.stack) != null) {
        // 弹出栈顶(CAS 将 stack 指向 h.next)
        if (f.stack == h && UNSAFE.compareAndSwapObject(f, STACK_OFFSET, h, h.next)) {
            // 执行任务
            h.run();
            // 注意:h.run() 可能会完成下游 Future,从而递归触发更下层的依赖
        }
    }
}

4.3.2 thenApply 原理

thenApply 方法会创建一个新的 CompletableFuture(下游)和一个 UniApply 节点,并将该节点推入上游的 stack 中(如果上游还未完成)。如果上游已经完成,则直接在当前线程执行转换。

java 复制代码
public <U> CompletableFuture<U> thenApply(Function<? super T,? extends U> fn) {
    CompletableFuture<U> d = new CompletableFuture<>();
    // 如果上游已完成,立即执行转换
    Object r = result;
    if (r != null) {
        if (r instanceof AltResult) {
            // 上游异常,传播异常
            d.completeExceptionally(((AltResult)r).ex);
        } else {
            try {
                // 正常转换
                d.complete(fn.apply((T) r));
            } catch (Throwable ex) {
                d.completeExceptionally(ex);
            }
        }
    } else {
        // 上游未完成,将节点压入栈中
        push(new UniApply<>(d, fn));
    }
    return d;
}

// 压栈操作(简化)
final void push(Completion c) {
    do {
        c.next = stack;
    } while (!UNSAFE.compareAndSwapObject(this, STACK_OFFSET, c.next, c));
}

static final class UniApply<T,V> extends Completion {
    CompletableFuture<V> dep;   // 下游 Future
    Function<? super T,? extends V> fn;
    
    // 当上游完成时,会调用 run()
    public void run() {
        CompletableFuture<T> src = ...; // 上游
        Object r = src.result;
        // 如果上游结果异常,下游也异常
        if (r instanceof AltResult) {
            dep.completeExceptionally(((AltResult)r).ex);
        } else {
            try {
                V v = fn.apply((T) r);
                dep.complete(v);
            } catch (Throwable ex) {
                dep.completeExceptionally(ex);
            }
        }
    }
}
4.3.2.1 case1--不同future执行thenApply方法

不同future执行thenApply方法,会按照thenApply的顺序依次执行

1、stage_1执行的时候,会新建一个futureA,然后执行push方法。由于stage_1.stack一开始是null,所以先执行futureA.next=null,然后将stage_1.stack指向futureA。所以stage_1的stack的单向链表是:stage_1.stack-->futureA。

2、同理futureA的stack的单向链表是:futureA.stack-->futureB。

因此stage_1执行完后,会执行栈的下一个futureA。futureA执行完之后,会执行futureB。

java 复制代码
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class OrderGuaranteeDemo2 {
    public static void main(String[] args) {
        // 创建一个单线程的线程池,保证任务按提交顺序依次执行
        ExecutorService singleExecutor = Executors.newSingleThreadExecutor();

        CompletableFuture<String> stage_1 = CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
            return "hello";
        });

        CompletableFuture<String> futureA = stage_1.thenApplyAsync(s -> {
            System.out.println("A 开始执行");
            return s + "A";
        }, singleExecutor);

        CompletableFuture<String> futureB = futureA.thenApplyAsync(s -> {
            System.out.println("B 开始执行");
            return s + "B";
        }, singleExecutor);

        // 等待所有任务完成,确保程序不会提前退出
        CompletableFuture.allOf(futureA, futureB).join();

        // 关闭线程池
        singleExecutor.shutdown();
    }
}

运行结果:

java 复制代码
A 开始执行
B 开始执行
4.3.2.2 case2--同一个future执行thenApply方法

同一个future执行thenApply方法,后执行thenApply的future会先抛出到线程池中,如果和下面的case一样,是单线程的话,一定可以看到先执行B,再执行A。但是如果不是单线程的线程池的话,则不能保证一定先执行B,因为线程的调度是由cpu决定的,从stack的代码层面来说,只是先抛出futureB,再抛出futureA。

1、stage_1执行的时候,会新建一个futureA,然后执行push方法。由于stage_1.stack一开始是null,所以先执行futureA.next=null,然后将stage_1.stack指向futureA。所以stage_1的stack的单向链表是:stage_1.stack-->futureA。

2、然后会新建futureB,然后执行push方法。先执行futureB.next=futureA,然后将stage_1.stack指向futureB。所以stage_1的stack的单向链表是:stage_1.stack-->futureB-->futureA。

3、stage_1执行完成后,会先将futureB抛出到线程池中进行执行,然后再将futureA抛出到线程池中执行。但是具体是谁先执行,这个要由cpu的调度来决定。

java 复制代码
import java.util.concurrent.*;

public class OrderGuaranteeDemo {
    public static void main(String[] args) throws Exception {
        // 创建一个单线程的线程池,保证任务按提交顺序依次执行
        ExecutorService singleExecutor = Executors.newSingleThreadExecutor();

        // 第一个阶段:异步返回 "hello"
        CompletableFuture<String> stage_1 = CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
            return "hello";
        });

        // 先注册的任务 A(理论上应后被弹出)
        CompletableFuture<String> futureA = stage_1.thenApplyAsync(s -> {
            System.out.println("A 开始执行");
            return s + "A";
        }, singleExecutor);

        // 后注册的任务 B(理论上应先被弹出并提交)
        CompletableFuture<String> futureB = stage_1.thenApplyAsync(s -> {
            System.out.println("B 开始执行");
            return s + "B";
        }, singleExecutor);

        // 等待所有任务完成,确保程序不会提前退出
        CompletableFuture.allOf(futureA, futureB).join();

        // 关闭线程池
        singleExecutor.shutdown();
    }
}

4.3.3 组合方法:thenCombine 原理

thenCombine 需要等待两个上游都完成。它创建 BiApply 节点,节点中持有两个上游 Future 和下游 Future。当任意一个上游完成时,都会尝试检查另一个是否也完成,如果都完成,则执行合并函数。

java 复制代码
public <U,V> CompletableFuture<V> thenCombine(
    CompletionStage<? extends U> other,
    BiFunction<? super T,? super U,? extends V> fn) {
    CompletableFuture<V> d = new CompletableFuture<>();
    // 创建 BiApply 节点,它会等待两个上游
    BiApply<T,U,V> node = new BiApply<>(d, fn);
    // 将自己注册到两个上游的栈中
    push(node);
    other.thenAcceptBoth(...); // 类似注册
    return d;
}

4.4.4 与核心 API 的对比理解

  1. thenApply:创建 UniApply 节点,推入上游 stack 同步执行转换,可能由上游完成线程执行。
  2. thenApplyAsync:创建 UniApply 节点,但节点内部将任务提交到线程池,总是异步执行转换。
  3. thenCombine:创建 BiApply 节点,同时注册到两个上游,等待两个上游都完成后执行合并。
  4. allOf:创建一个新的 CompletableFuture,内部用计数器等待所有子任务完成,任一异常则立即完成异常。
  5. anyOf:创建新的 CompletableFuture,任意子任务完成时立即完成,结果由最先完成的子任务决定。
相关推荐
sheji34162 小时前
【开题答辩全过程】以 校园帮系统为例,包含答辩的问题和答案
java·spring boot
填满你的记忆2 小时前
《Java 面试常见题型(2026最新版,背完直接能面)》
java·开发语言
小松加哲2 小时前
# Spring Aware 与 BeanPostProcessor:作用、使用与原理(源码级)
java·后端·spring
:mnong2 小时前
附图报价系统设计分析2
python·pyqt·openvino
源码之家2 小时前
计算机毕业设计:基于Python的美食推荐可视化系统 Django框架 可视化 协同过滤推荐算法 推荐系统 食物 食品 大数据 数据分析(建议收藏)✅
python·django·flask·课程设计·推荐算法·美食
人还是要有梦想的2 小时前
QT的基本学习路线
开发语言·qt·学习
小松加哲2 小时前
Spring AOP 代理创建时机深度解析:初始化阶段 vs 三级缓存(源码级)
java·spring·缓存
源码之家2 小时前
计算机毕业设计:基于Python的美食数据采集可视化系统 Django框架 Scrapy爬虫 可视化 数据分析 大数据 机器学习 食物 食品(建议收藏)✅
python·算法·机器学习·信息可视化·课程设计
Mr_Xuhhh2 小时前
LeetCode 热题 100 刷题笔记:从数组到字符串的经典解法(续)
java·数据结构·算法