【Java 并发编程】Future 和 CompletableFuture 详解:异步任务、链式调用、任务编排与面试总结

摘要

在 Java 并发编程中,FutureCompletableFuture 都是用来处理异步任务结果的重要工具。

Future 是 Java 5 引入的异步结果模型,主要用于获取线程池中异步任务的执行结果。

CompletableFuture 是 Java 8 引入的增强版异步编程工具,不仅可以获取异步结果,还支持链式调用、任务编排、多个任务组合以及异常处理。

本文将从面试角度出发,详细讲解 FutureCompletableFuture 的作用、基本使用、核心方法、区别、项目场景以及面试回答方式。

目录


一、为什么需要异步任务?

在实际项目中,很多操作是比较耗时的,比如:

查询数据库
调用第三方接口
调用 RPC 服务
发送短信
生成报表
文件上传下载

如果这些任务都由主线程同步执行,那么主线程就会一直阻塞等待,接口响应时间会变长,系统吞吐量也会下降。

比如一个商品详情页接口,需要查询:

商品基本信息:50ms
库存信息:80ms
优惠券信息:100ms
评价信息:120ms
推荐商品:150ms

如果串行执行,总耗时大概是:

50 + 80 + 100 + 120 + 150 = 500ms

但是这些查询之间如果没有依赖关系,就可以并行执行。

这样总耗时接近最慢的那个任务,也就是大约 150ms

这就是异步编程的价值:

把多个互不依赖的耗时任务并行执行,提高系统吞吐量,降低接口响应时间。

二、Future 是什么?

Future 可以理解成:

异步任务执行结果的占位符。

当我们把一个任务提交给线程池后,任务不一定马上执行完成。

但是线程池会立即返回一个 Future 对象。

这个 Future 对象代表未来某个时间点可以拿到的任务结果。

简单来说:

任务提交时,结果还没出来;
Future 先返回;
等任务执行完成后,可以通过 Future 获取结果。

三、Future 的基本使用

Future 通常配合线程池和 Callable 使用。

import java.util.concurrent.*;

public class FutureDemo {
    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newFixedThreadPool(2);

        Future<String> future = executorService.submit(() -> {
            Thread.sleep(1000);
            return "查询用户成功";
        });

        System.out.println("主线程继续执行");

        String result = future.get();

        System.out.println("异步任务结果:" + result);

        executorService.shutdown();
    }
}

执行流程如下:

1. 主线程把任务提交给线程池
2. 线程池中的子线程执行任务
3. 主线程可以继续执行其他逻辑
4. 当主线程需要结果时,调用 future.get()
5. 如果异步任务还没执行完,get() 会阻塞等待
6. 子线程执行完成后,主线程拿到结果

四、Future 常用方法

1. get()

get() 用来获取异步任务的执行结果。

String result = future.get();

如果任务还没有执行完成,调用 get() 的线程会阻塞等待。


2. get(timeout, unit)

带超时时间获取结果。

String result = future.get(2, TimeUnit.SECONDS);

如果 2 秒内任务还没有返回结果,就会抛出 TimeoutException

在实际项目中,建议使用带超时时间的 get(),避免线程无限等待。


3. isDone()

判断任务是否执行完成。

if (future.isDone()) {
    String result = future.get();
}

4. cancel()

取消任务。

future.cancel(true);

参数 true 表示如果任务正在运行,尝试中断执行该任务的线程。


5. isCancelled()

判断任务是否被取消。

boolean cancelled = future.isCancelled();

五、Future 的缺点

虽然 Future 可以获取异步任务的结果,但是它也有一些明显缺点。


1. get() 会阻塞

这是 Future 最大的问题。

String result = future.get();

如果任务还没有完成,当前线程会一直阻塞等待。

这样异步代码在获取结果时又变成了同步等待。


2. 不支持链式调用

假设有这样一个需求:

先查询用户信息
再根据用户信息查询订单
再根据订单信息计算优惠

使用 Future 实现时比较麻烦,因为 Future 本身不支持链式调用。


3. 不方便组合多个异步任务

比如现在有三个任务:

查询用户信息
查询订单信息
查询优惠券信息

如果使用 Future,通常需要一个个调用 get() 获取结果,代码不够优雅。


4. 异常处理不方便

Future 的异常通常要在 get() 的时候捕获。

try {
    String result = future.get();
} catch (ExecutionException e) {
    Throwable cause = e.getCause();
}

异常处理和业务逻辑容易混在一起。


六、CompletableFuture 是什么?

CompletableFuture 是 Java 8 引入的异步编程工具。

它实现了 Future 接口,可以看作是 Future 的增强版。

CompletableFuture 不仅可以获取异步结果,还支持:

链式调用
任务串行编排
任务并行组合
多个任务结果合并
异常处理
手动完成任务

所以在复杂异步场景下,CompletableFutureFuture 更常用。


七、CompletableFuture 的基本使用

1. runAsync:没有返回值的异步任务

import java.util.concurrent.CompletableFuture;

public class CompletableFutureDemo {
    public static void main(String[] args) {
        CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
            System.out.println("异步执行任务");
        });

        future.join();
    }
}

runAsync() 适合执行没有返回值的异步任务,比如:

异步打印日志
异步发送通知
异步缓存预热

2. supplyAsync:有返回值的异步任务

import java.util.concurrent.CompletableFuture;

public class CompletableFutureDemo {
    public static void main(String[] args) {
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            return "查询用户成功";
        });

        String result = future.join();

        System.out.println(result);
    }
}

supplyAsync() 适合执行有返回值的异步任务。


八、get 和 join 的区别

CompletableFuture 既可以使用 get(),也可以使用 join() 获取结果。

future.get();
future.join();

它们的区别如下:

方法 说明
get() 会抛出受检异常,需要 try-catch 或 throws
join() 抛出运行时异常,不强制捕获

示例:

try {
    String result = future.get();
} catch (Exception e) {
    e.printStackTrace();
}

使用 join()

String result = future.join();

面试中可以这样说:

get 和 join 都会阻塞等待结果。
区别是 get 会抛受检异常,join 会把异常包装成运行时异常 CompletionException。

九、CompletableFuture 的链式调用

CompletableFutureFuture 强大的地方就在于它支持链式调用。


1. thenApply:接收上一步结果,并返回新结果

CompletableFuture<String> future = CompletableFuture
        .supplyAsync(() -> {
            return "user";
        })
        .thenApply(user -> {
            return user + " order";
        });

System.out.println(future.join());

执行流程:

第一步返回 user
第二步拿到 user,处理成 user order

thenApply 适合对上一步结果做转换。


2. thenAccept:接收上一步结果,但没有返回值

CompletableFuture<Void> future = CompletableFuture
        .supplyAsync(() -> {
            return "user";
        })
        .thenAccept(user -> {
            System.out.println("拿到用户:" + user);
        });

future.join();

thenAccept 适合消费结果,比如打印日志、发送消息等。


3. thenRun:不接收上一步结果,也没有返回值

CompletableFuture<Void> future = CompletableFuture
        .supplyAsync(() -> {
            return "user";
        })
        .thenRun(() -> {
            System.out.println("继续执行下一步");
        });

future.join();

thenRun 不关心上一步的执行结果,只是在上一步完成之后继续执行。


4. thenApply、thenAccept、thenRun 的区别

方法 是否接收上一步结果 是否有返回值
thenApply 接收
thenAccept 接收
thenRun 不接收

可以这样记:

thenApply:有入参,有返回值
thenAccept:有入参,无返回值
thenRun:无入参,无返回值

十、thenCompose:处理有依赖关系的异步任务

如果第二个异步任务依赖第一个异步任务的结果,可以使用 thenCompose

比如:

先查用户
再根据用户查订单

代码如下:

CompletableFuture<String> future = CompletableFuture
        .supplyAsync(() -> {
            return "userId=1001";
        })
        .thenCompose(userId -> {
            return CompletableFuture.supplyAsync(() -> {
                return "订单信息," + userId;
            });
        });

System.out.println(future.join());

面试中可以这样说:

thenCompose 用来处理有依赖关系的异步任务,后一个任务依赖前一个任务的结果。

十一、thenCombine:合并两个独立任务的结果

如果两个任务互不依赖,可以并行执行,然后合并结果。

比如:

查询用户信息
查询订单信息
两个任务可以并行执行
最后合并结果

代码如下:

CompletableFuture<String> userFuture = CompletableFuture.supplyAsync(() -> {
    return "用户信息";
});

CompletableFuture<String> orderFuture = CompletableFuture.supplyAsync(() -> {
    return "订单信息";
});

CompletableFuture<String> resultFuture = userFuture.thenCombine(orderFuture, (user, order) -> {
    return user + " + " + order;
});

System.out.println(resultFuture.join());

面试中可以这样说:

thenCombine 适合两个没有依赖关系的异步任务并行执行,然后把两个任务结果合并。

十二、allOf:等待多个任务全部完成

如果有多个异步任务,需要等待它们全部完成,可以使用 allOf()

CompletableFuture<String> userFuture =
        CompletableFuture.supplyAsync(() -> "用户信息");

CompletableFuture<String> orderFuture =
        CompletableFuture.supplyAsync(() -> "订单信息");

CompletableFuture<String> couponFuture =
        CompletableFuture.supplyAsync(() -> "优惠券信息");

CompletableFuture<Void> allFuture = CompletableFuture.allOf(
        userFuture,
        orderFuture,
        couponFuture
);

allFuture.join();

String user = userFuture.join();
String order = orderFuture.join();
String coupon = couponFuture.join();

System.out.println(user);
System.out.println(order);
System.out.println(coupon);

allOf() 非常适合接口聚合场景。

比如商品详情页接口,需要同时查询:

商品基本信息
库存信息
优惠券信息
评论信息
推荐商品

这些任务互不依赖,就可以并行查询。


十三、anyOf:任意一个任务完成即可

anyOf() 表示多个任务中,只要有一个任务完成,就返回结果。

CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
    sleep(3000);
    return "接口1返回";
});

CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
    sleep(1000);
    return "接口2返回";
});

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

System.out.println(anyFuture.join());

辅助方法:

private static void sleep(long millis) {
    try {
        Thread.sleep(millis);
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    }
}

anyOf() 适合:

多个数据源竞争
多个接口取最快返回
容灾兜底

十四、CompletableFuture 的异常处理

1. exceptionally:异常兜底

如果前面的任务发生异常,可以使用 exceptionally 返回一个默认结果。

CompletableFuture<String> future = CompletableFuture
        .supplyAsync(() -> {
            int i = 1 / 0;
            return "success";
        })
        .exceptionally(e -> {
            return "默认结果";
        });

System.out.println(future.join());

输出:

默认结果

2. handle:成功或失败都会执行

handle 可以同时处理正常结果和异常。

CompletableFuture<String> future = CompletableFuture
        .supplyAsync(() -> {
            int i = 1 / 0;
            return "success";
        })
        .handle((result, exception) -> {
            if (exception != null) {
                return "异常兜底结果";
            }
            return result;
        });

System.out.println(future.join());

3. whenComplete:任务完成后回调

whenComplete 可以在任务完成后执行一些额外逻辑,比如打日志、监控、清理资源。

CompletableFuture<String> future = CompletableFuture
        .supplyAsync(() -> {
            return "success";
        })
        .whenComplete((result, exception) -> {
            System.out.println("任务完成,result = " + result);
        });

System.out.println(future.join());

需要注意:

whenComplete 通常不改变原来的执行结果。

4. 三种异常处理方式区别

方法 是否能感知异常 是否能返回新结果 典型用途
exceptionally 可以 可以 异常兜底
handle 可以 可以 成功失败都处理
whenComplete 可以 通常不改变结果 打日志、监控、清理资源

十五、Async 和非 Async 的区别

CompletableFuture 很多方法都有两个版本:

thenApply()
thenApplyAsync()

区别如下:

1. thenApply

future.thenApply(result -> {
    return result + " after";
});

不带 Async 的方法,通常由上一个阶段的线程继续执行。


2. thenApplyAsync

future.thenApplyAsync(result -> {
    return result + " after";
});

Async 的方法,会把后续任务提交到线程池中异步执行。

如果不指定线程池,默认使用:

ForkJoinPool.commonPool()

实际项目中更推荐传入自定义线程池:

future.thenApplyAsync(result -> {
    return result + " after";
}, executor);

面试中可以这样回答:

不带 Async 的方法通常由前一个阶段的线程继续执行;
带 Async 的方法会把任务提交到线程池异步执行。
如果不指定线程池,默认使用 commonPool,生产环境最好显式指定自定义线程池。

十六、为什么 CompletableFuture 要指定自定义线程池?

如果直接这样写:

CompletableFuture.supplyAsync(() -> {
    return "hello";
});

默认使用的是:

ForkJoinPool.commonPool()

这个公共线程池是全局共享的。

实际项目中不建议大量业务都使用默认公共线程池,原因有:

1. 不同业务共用一个线程池,容易互相影响
2. 线程数不可控
3. 线程名不清晰,不方便线上排查
4. 不方便做业务隔离和监控

推荐写法:

import java.util.concurrent.*;

public class CompletableFutureThreadPoolDemo {
    public static void main(String[] args) {
        ExecutorService executor = new ThreadPoolExecutor(
                8,
                16,
                60L,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(1000),
                r -> new Thread(r, "order-async-pool-" + r.hashCode()),
                new ThreadPoolExecutor.CallerRunsPolicy()
        );

        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            return "查询订单";
        }, executor);

        System.out.println(future.join());

        executor.shutdown();
    }
}

面试中可以这样说:

实际项目中使用 CompletableFuture 时,我会传入自定义线程池,而不是使用默认的 ForkJoinPool.commonPool。
这样可以避免不同业务互相影响,也方便线程池隔离、监控和问题排查。

十七、实际项目场景:商品详情页接口聚合

比如电商系统中,商品详情页需要返回:

商品基本信息
库存信息
优惠券信息
评价信息
推荐商品

如果串行查询:

商品信息 50ms
库存信息 80ms
优惠券 100ms
评价 120ms
推荐 150ms

总耗时大约 500ms

如果使用 CompletableFuture 并行查询,总耗时接近最慢的任务:

总耗时大约 150ms

示例代码:

import java.util.concurrent.*;

public class ProductDetailDemo {

    private static final ExecutorService executor = new ThreadPoolExecutor(
            10,
            20,
            60L,
            TimeUnit.SECONDS,
            new ArrayBlockingQueue<>(1000),
            r -> new Thread(r, "product-detail-pool-" + r.hashCode()),
            new ThreadPoolExecutor.CallerRunsPolicy()
    );

    public static void main(String[] args) {
        CompletableFuture<String> productFuture =
                CompletableFuture.supplyAsync(() -> queryProduct(), executor);

        CompletableFuture<String> stockFuture =
                CompletableFuture.supplyAsync(() -> queryStock(), executor);

        CompletableFuture<String> couponFuture =
                CompletableFuture.supplyAsync(() -> queryCoupon(), executor);

        CompletableFuture<String> commentFuture =
                CompletableFuture.supplyAsync(() -> queryComment(), executor);

        CompletableFuture<Void> allFuture = CompletableFuture.allOf(
                productFuture,
                stockFuture,
                couponFuture,
                commentFuture
        );

        allFuture.join();

        String product = productFuture.join();
        String stock = stockFuture.join();
        String coupon = couponFuture.join();
        String comment = commentFuture.join();

        System.out.println(product);
        System.out.println(stock);
        System.out.println(coupon);
        System.out.println(comment);

        executor.shutdown();
    }

    private static String queryProduct() {
        sleep(50);
        return "商品信息";
    }

    private static String queryStock() {
        sleep(80);
        return "库存信息";
    }

    private static String queryCoupon() {
        sleep(100);
        return "优惠券信息";
    }

    private static String queryComment() {
        sleep(120);
        return "评价信息";
    }

    private static void sleep(long millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

这个案例在面试中非常好用。

可以这样描述:

商品详情页接口需要查询多个下游服务,这些服务之间没有依赖关系。
如果串行查询,接口耗时会比较长。
所以可以使用 CompletableFuture 并行查询,然后通过 allOf 等待全部完成,最后统一组装结果。
这样可以把总耗时从多个接口耗时之和,优化成接近最慢的那个接口耗时。

十八、Future 和 CompletableFuture 的区别

对比点 Future CompletableFuture
出现时间 Java 5 Java 8
是否能获取异步结果 可以 可以
获取结果是否阻塞 get 会阻塞 get/join 也会阻塞
是否支持链式调用 不支持 支持
是否支持任务组合 不方便 支持
是否支持任务编排 不方便 支持
异常处理 不方便 支持 exceptionally、handle、whenComplete
多任务合并 不方便 支持 allOf、anyOf、thenCombine
使用场景 简单异步任务 复杂异步编排

十九、面试回答:Future 怎么说?

如果面试官问 Future,可以这样回答:

Future 是 Java 早期提供的异步结果接口,通常配合 Callable 和线程池使用。
提交一个 Callable 任务后,线程池会返回一个 Future 对象。
Future 表示未来某个时间点可以拿到的任务结果。

我们可以通过 get 方法获取结果,通过 isDone 判断任务是否完成,通过 cancel 取消任务。
但是 Future 的缺点是 get 方法会阻塞,不支持链式调用,也不方便组合多个异步任务,异常处理也不够优雅。
所以复杂异步场景下更常用 CompletableFuture。

二十、面试回答:CompletableFuture 怎么说?

如果面试官问 CompletableFuture,可以这样回答:

CompletableFuture 是 Java 8 引入的异步编程工具,它实现了 Future 接口,可以看作是 Future 的增强版。

它不仅可以获取异步任务结果,还支持链式调用、任务编排、多个任务组合和异常处理。
比如 thenApply 可以对上一步结果做转换,thenAccept 可以消费结果,thenCompose 可以处理有依赖关系的异步任务,thenCombine 可以合并两个并行任务结果,allOf 可以等待多个任务全部完成,anyOf 可以等待任意一个任务完成。

异常方面,可以通过 exceptionally、handle、whenComplete 进行处理。
实际项目中,CompletableFuture 常用于接口聚合、并行查询、异步调用下游服务等 IO 密集型场景。
使用时要注意传入自定义线程池,不要直接使用默认的 ForkJoinPool.commonPool,避免线程不可控和业务之间互相影响。

二十一、最终总结

FutureCompletableFuture 都是 Java 中处理异步任务结果的重要工具。

Future 是基础版,主要用于:

提交异步任务
获取异步结果
判断任务是否完成
取消任务

但是它的缺点是:

get 会阻塞
不支持链式调用
不方便组合多个任务
异常处理不够优雅

CompletableFuture 是增强版,除了具备 Future 的能力,还支持:

链式调用
串行任务编排
并行任务组合
多个任务结果合并
异常兜底处理

所以在实际项目中:

简单异步任务可以使用 Future;
复杂异步编排、接口聚合、并行查询更推荐使用 CompletableFuture。

尤其在电商、订单、商品详情页、数据聚合接口中,CompletableFuture 非常常见。

最后需要注意:

生产环境使用 CompletableFuture 时,最好指定自定义线程池,不要直接使用默认 commonPool。

二十二、最精简面试背诵版

Future 和 CompletableFuture 都是用来处理异步任务结果的。

Future 是 Java 5 提供的基础接口,通常配合 Callable 和线程池使用。
它可以通过 get 获取结果,通过 isDone 判断是否完成,通过 cancel 取消任务。
但是 Future 的 get 是阻塞的,不支持链式调用,也不方便组合多个异步任务。

CompletableFuture 是 Java 8 引入的增强版 Future。
它不仅可以获取异步结果,还支持链式调用、任务编排、多个任务组合和异常处理。
比如 thenApply 做结果转换,thenCompose 做串行依赖,thenCombine 合并两个任务结果,allOf 等待多个任务全部完成,anyOf 等待任意一个任务完成。
异常处理可以使用 exceptionally、handle、whenComplete。

实际项目中,如果只是简单提交一个任务并等待结果,用 Future 就够了;
如果涉及多个异步任务的并行执行、依赖编排、结果合并和异常兜底,更适合使用 CompletableFuture。

并且在生产环境使用 CompletableFuture 时,最好传入自定义线程池,而不是使用默认的 ForkJoinPool.commonPool。
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐