admin管理员组

文章数量:1037775

大厂常用工具类Completablefuture的面试题都有哪些?这次我都替你收集好了。

大家好,我是程序员牛肉。

最近在复习自己的简历,发现Completablefuture这个工具类怎么突然这么爱考了,牛客上的面经基本都在问这个工具类。因此今天我们就来专门总结一下Completablefuture常见的面试题。

关于什么是Completablefuture,我在之前有写过几篇文章。因此我们这篇文章就不做赘述了。如果你还不知道什么是Completablefuture的话可以看看下面的文章:

下次换你来拷打面试官!一文带你读懂企业常用异步编程核心工具类CompletableFuture

2025-01-29

还在用Future搞异步?快看看企业中常用的CompletableFuture是怎么用的!

2024-11-19

让我们进入正题,看看有哪些题是面试官爱考的。

01、Completablefuture的底层原理是什么,为什么它可以实现任务的编排?

CompletableFuture内部维护了一个volatile修饰的result字段存储计算结果或异常,以及一个Completion类型的stack字段构成的依赖链栈。每个Completion对象代表一个待触发的依赖任务(如thenApplythenAccept等方法创建的任务),通过链表结构串联形成任务编排的流水线。

当我们使用Completablefuture来编排异步任务的时候,我们可以认为本质上是维护了一个栈类型的链表:

同级的任务在一个栈中。各个栈之间使用链表相连。

比如当我们使用Completablefuture进行如下任务编排的时候:

代码语言:javascript代码运行次数:0运行复制
CompletableFuture.supplyAsync(() -> 10)
                 .thenApply(r -> r * 1)
                 .thenApply(r -> r * 2)
                 .thenApply(r -> r * 3);

本质上在内部就维护了下图所示的一个链表:

每一次thenApply方法都会创建出一个completion,这个completion除了我们的lambda函数之外,还会有两个指针指向它的前置future和下一个future,所以当一个子任务完成之后,他将通过指针寻找自己的下一个completion对象,直至到末尾。

[整个过程通过 stack 链表构建了 CompletableFuture 之间的依赖关系,当一个 CompletableFuture 完成时,会根据 stack 链表依次触发后续依赖操作。每个 CompletableFuture 的计算结果存储在 result 字段中,通过这种方式实现了异步任务的链式调用和结果传递。]

02、Completablefuture都有哪些常用方法?

1.创建异步任务

代码语言:javascript代码运行次数:0运行复制
//supplyAsync()
//执行有返回值的异步任务,默认使用 ForkJoinPool。
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    return "Hello World";
});

//runAsync()
//执行无返回值的异步任务。
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
    System.out.println("Task is running");
});

2.链式处理结果

代码语言:javascript代码运行次数:0运行复制
//thenApply()
//对结果进行转换,返回新值。
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello")
    .thenApply(s -> s + " World"); // 结果为 "Hello World"
    
//thenAccept()
//消费结果(无返回值)。
CompletableFuture.supplyAsync(() -> "Hello")
    .thenAccept(s -> System.out.println(s)); // 输出 "Hello"

//thenRun()
//任务完成后执行一个动作(不依赖结果)。
CompletableFuture.supplyAsync(() -> "Hello")
    .thenRun(() -> System.out.println("Task Done"));

3.组合多个future

代码语言:javascript代码运行次数:0运行复制
//thenCompose()
//扁平化嵌套的 CompletableFuture。
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello")
    .thenCompose(s -> CompletableFuture.supplyAsync(() -> s + " World"));
    
    
//thenCombine()
//合并两个独立 Future 的结果
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> " World");
CompletableFuture<String> combined = future1.thenCombine(future2, (s1, s2) -> s1 + s2);

4.多任务协作

代码语言:javascript代码运行次数:0运行复制
//allOf()
//等待所有 Future 完成。
CompletableFuture<Void> all = CompletableFuture.allOf(future1, future2);
all.thenRun(() -> System.out.println("All tasks done"));


//anyOf()
//任意一个 Future 完成即触发。
CompletableFuture<Object> any = CompletableFuture.anyOf(future1, future2);
any.thenAccept(result -> System.out.println("First result: " + result));

5.超时控制:

代码语言:javascript代码运行次数:0运行复制
//orTimeout()
//超时抛出 TimeoutException。
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> longTask())
    .orTimeout(1, TimeUnit.SECONDS);
    
    
//completeOnTimeout()
//超时返回默认值。
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> longTask())
    pleteOnTimeout("Timeout", 1, TimeUnit.SECONDS);

03、如何使用Completablefuture实现调用接口的时候有超时控制机制,如果接口超时就取消执行任务的线程?

这个题本质上还是在考察我们对Completablefuture相关方法的熟练程度。设计这个功能其实并不困难。

从逻辑的角度讲,应该是设置超时时间,当接口调用超时之后就抛异常。之后我们在catch中手动的捕捉这个异常并且中断相关的线程。

而基于Completablefuture的相关方法,具体的代码设计为:先通过orTimeout方法来实现。如果接口调用超时,可以通过cancel方法来中断执行任务的线程。

代码语言:javascript代码运行次数:0运行复制
public class CompletableFutureTimeoutExample {
    public static void main(String[] args) {
        // 创建CompletableFuture来执行异步任务
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            // 模拟接口调用
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
            return"Response from the server";
        });

        // 设置超时时间
        CompletableFuture<String> timeoutFuture = future.orTimeout(3, TimeUnit.SECONDS);

        try {
            // 获取结果,如果超时会抛出TimeoutException
            String result = timeoutFuture.get();
            System.out.println("Result: " + result);
        } catch (TimeoutException e) {
            System.out.println("Timeout occurred, interrupting the task.");
            // 取消任务,中断线程
            future.cancel(true);
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    }
}

04、Completablefuture是如何实现带有超时时间的等待任务的?

前面我们说过Completablefuture内部会有一个volatile修饰的result字段存储计算结果或异常。在使用get方法的时候,先对result进行判断。如果result此时为空,就说明对应的异步任务还没有执行,调用timeGet方法进行后续操作。

代码语言:javascript代码运行次数:0运行复制
    public T get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException {
        
        //超时时间(我们输入的是秒,在这里要转化成为纳秒)
        long nanos = unit.toNanos(timeout);
        Object r;
        //如果这个异步任务还没有执行,调用timeGet进行执行
        if ((r = result) == null)
            r = timedGet(nanos);
        return (T) reportGet(r);
    }

这个方法当对应的completablefuture任务还没有执行的时候,就会使用timeget方法。

那我们先不着急看timeGet的源码,先自己理一理timeGet的逻辑:

get方法的意思是超时等待。那既然result结果为空就要进入timeGet方法进行超时等待了。所以timeGet方法的大致逻辑应该就是一直等待对应的异步任务有执行结果(result不为空)或者等待超时。让我们来看源码:

首先判断当前的线程有没有被中断,如果你都被中断了就直接返回null。

之后再看传递过来的时间参数是否异常,不能小于或者等于0。如果小于或者等于0的话直接抛出超时异常:

让我们继续看如果传递的参数是正常的逻辑,先对传递过来的超时参数进行了处理,让其加上当前的纳秒数。获取到一个以纳秒为单位的截止时间。

之后为了取得有效时间,又进行了一次判断。如果d==0的话,就给他赋值1纳秒。

并且我们创建了一个等待节点Singnaller,封装当前线程、剩余超时时间和截止时间。

之后就开始死等了,外层就是一个while循环,循环结束的条件是result不为空:

让我们开始看内部的代码,首先当对应的等待节点q为空的时候就开始封装对应的等待节点,添加了封装当前线程、剩余超时时间和截止时间。

这里其实是一个优化后的阻塞操作。当我们封装好了对应的q节点之后,最简单的操作就是直接挂起对应的线程,等待任务的执行。

但如果当前的线程是ForkJoinWorkerThread(FixedThreadPool的工作线程)

的时候,为了避免大量的工作线程都被阻塞而导致任务阻塞的情况,我们就使用helpAsyncBlocker方法来让线程在等待期间协作执行其他任务,避免完全挂起,从而提升资源利用率。

问题来了:为啥这里要单独对ForkJoinWorkerThread这个线程做优化呢?

原因是因为Completablefuture的场景本来就适合异步任务的编排。而如果未手动指定线程池,所有所有 supplyAsyncthenApply 等操作默认由 ForkJoinPoolForkJoinWorkerThread 执行。

代码语言:javascript代码运行次数:0运行复制
CompletableFuture.supplyAsync(() -> doWork())
    .thenAccept(result -> {
        CompletableFuture<Void> nestedFuture = CompletableFuture.runAsync(() -> {
            // 另一个耗时操作
            processResult(result);
        });

        try {
            // 在回调中使用带超时的 get()
            nestedFuture.get(3, TimeUnit.SECONDS); 
        } catch (TimeoutException e) {
            System.out.println("嵌套任务超时!");
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    });

因此我们要单独对ForkJoinPool的线程进行优化。但需要注意的是:Completablefuture只有在两核以及两核以上的服务器中使用ForkJoinPool,如果不符合这个条件,它使用的是:ThreadPerTaskExecutor。(这个后面会详细说

让我们继续回到代码:

在做了优化之后,将对应的的等待节点压入栈中,并且进行超时检查:

之后开始阻塞当前的线程:

阻塞的时候使用的是managedBlock这个方法,在这里我们只关注这个block方法:

由于我们传递的是等待节点Singnaller,所以他在这里实际上调用的是等待节点Singnaller的block方法:

这一看太熟悉了,这不是park方法吗?如果没有超时时间就是用park,如果有超时时间就是使用带有超时时间的parkNanos。

而timedget的后面那些就是进行一些删除操作,清除掉已经超时的无效等待节点之类的操作:

所以说Completablefuture带有超时时间的get的底层设计思路就是:如果当前线程是forkJoinPool的线程,就使用helpAsyncBlocker来进行挂起并优化。

如果是普通线程的话,就先将其加入到栈中,然后直接使用带有超时时间的park方法将其挂起。

最后再清除一些栈中的无效节点,做一下内存优化工作。

05、在上面的问题中,你提到了Completablefuture的线程池,你能详细的讲一讲吗?

从代码中我们可以看出它内部的线程池一共有两种:forkJoinPool和threadPerTaskExecutor。

我们可以看到:默认线程池是根据USE_COMMON_POOL这个布尔值来进行选择的。

  • 如果 USE_COMMON_POOLtrue,则 ASYNC_POOL 被设置为 ForkJoinPoolmonPool()
  • 如果 USE_COMMON_POOLfalse,则 ASYNC_POOL 被设置为一个 ThreadPerTaskExecutor

[ThreadPerTaskExecutor 是一个特殊的线程池实现,其核心设计思想是为每个提交的任务创建一个独立的线程来执行。这种设计特别适合轻量级任务的执行,理论上讲可以创建无数个线程,但实际还是受限于系统资源。]

那么USE_COMMON_POOL是如何进行取值的呢?

它使用的是getCommonPoolPrarllelism来进行的判断,这个方法返回公共线程池的并行度,默认情况下,公共线程池的并行度等于系统的可用处理器数量。

默认情况下,ForkJoinPoolmonPool 的并行度等于系统的可用处理器数量减去1。

  • 假设你的系统有 4 个核心,ForkJoinPool.getCommonPoolParallelism() 返回 3,那么 USE_COMMON_POOL 将被设置为 true,此时的默认线程池为ForkJoinPool。
  • 假设你的系统有两个核心,ForkJoinPool.getCommonPoolParallelism() 返回 1,那么 USE_COMMON_POOL 将被设置为 false,此时的默认线程池为:ThreadPerTaskExecutor 。

也就是说CompletableFuture的默认线程池只有在双核以上的机器内才会使用。在双核及以下的机器中,会为每个任务创建一个新线程,等于没有使用线程池,且有资源耗尽的风险。

而且吧,就算是ForkJoinPool也会有很多槽点,因此在使用Complefuturetable的时候,强烈推荐使用自定义线程池。

终于讲完了,差点累死我。这些内容足以应对90%的Completablefuture面试题了。我也极力推荐大家多去看一看源码,对个人的能力提升还是挺大的。

今天的文章就聊到这里了,相信通过我的介绍,你已经大致了解了这些常问的面试题。希望我的文章可以帮到你。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。原始发表:2025-03-14,如有侵权请联系 cloudcommunity@tencent 删除异步异常工具类线程线程池

大厂常用工具类Completablefuture的面试题都有哪些?这次我都替你收集好了。

大家好,我是程序员牛肉。

最近在复习自己的简历,发现Completablefuture这个工具类怎么突然这么爱考了,牛客上的面经基本都在问这个工具类。因此今天我们就来专门总结一下Completablefuture常见的面试题。

关于什么是Completablefuture,我在之前有写过几篇文章。因此我们这篇文章就不做赘述了。如果你还不知道什么是Completablefuture的话可以看看下面的文章:

下次换你来拷打面试官!一文带你读懂企业常用异步编程核心工具类CompletableFuture

2025-01-29

还在用Future搞异步?快看看企业中常用的CompletableFuture是怎么用的!

2024-11-19

让我们进入正题,看看有哪些题是面试官爱考的。

01、Completablefuture的底层原理是什么,为什么它可以实现任务的编排?

CompletableFuture内部维护了一个volatile修饰的result字段存储计算结果或异常,以及一个Completion类型的stack字段构成的依赖链栈。每个Completion对象代表一个待触发的依赖任务(如thenApplythenAccept等方法创建的任务),通过链表结构串联形成任务编排的流水线。

当我们使用Completablefuture来编排异步任务的时候,我们可以认为本质上是维护了一个栈类型的链表:

同级的任务在一个栈中。各个栈之间使用链表相连。

比如当我们使用Completablefuture进行如下任务编排的时候:

代码语言:javascript代码运行次数:0运行复制
CompletableFuture.supplyAsync(() -> 10)
                 .thenApply(r -> r * 1)
                 .thenApply(r -> r * 2)
                 .thenApply(r -> r * 3);

本质上在内部就维护了下图所示的一个链表:

每一次thenApply方法都会创建出一个completion,这个completion除了我们的lambda函数之外,还会有两个指针指向它的前置future和下一个future,所以当一个子任务完成之后,他将通过指针寻找自己的下一个completion对象,直至到末尾。

[整个过程通过 stack 链表构建了 CompletableFuture 之间的依赖关系,当一个 CompletableFuture 完成时,会根据 stack 链表依次触发后续依赖操作。每个 CompletableFuture 的计算结果存储在 result 字段中,通过这种方式实现了异步任务的链式调用和结果传递。]

02、Completablefuture都有哪些常用方法?

1.创建异步任务

代码语言:javascript代码运行次数:0运行复制
//supplyAsync()
//执行有返回值的异步任务,默认使用 ForkJoinPool。
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    return "Hello World";
});

//runAsync()
//执行无返回值的异步任务。
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
    System.out.println("Task is running");
});

2.链式处理结果

代码语言:javascript代码运行次数:0运行复制
//thenApply()
//对结果进行转换,返回新值。
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello")
    .thenApply(s -> s + " World"); // 结果为 "Hello World"
    
//thenAccept()
//消费结果(无返回值)。
CompletableFuture.supplyAsync(() -> "Hello")
    .thenAccept(s -> System.out.println(s)); // 输出 "Hello"

//thenRun()
//任务完成后执行一个动作(不依赖结果)。
CompletableFuture.supplyAsync(() -> "Hello")
    .thenRun(() -> System.out.println("Task Done"));

3.组合多个future

代码语言:javascript代码运行次数:0运行复制
//thenCompose()
//扁平化嵌套的 CompletableFuture。
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello")
    .thenCompose(s -> CompletableFuture.supplyAsync(() -> s + " World"));
    
    
//thenCombine()
//合并两个独立 Future 的结果
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> " World");
CompletableFuture<String> combined = future1.thenCombine(future2, (s1, s2) -> s1 + s2);

4.多任务协作

代码语言:javascript代码运行次数:0运行复制
//allOf()
//等待所有 Future 完成。
CompletableFuture<Void> all = CompletableFuture.allOf(future1, future2);
all.thenRun(() -> System.out.println("All tasks done"));


//anyOf()
//任意一个 Future 完成即触发。
CompletableFuture<Object> any = CompletableFuture.anyOf(future1, future2);
any.thenAccept(result -> System.out.println("First result: " + result));

5.超时控制:

代码语言:javascript代码运行次数:0运行复制
//orTimeout()
//超时抛出 TimeoutException。
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> longTask())
    .orTimeout(1, TimeUnit.SECONDS);
    
    
//completeOnTimeout()
//超时返回默认值。
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> longTask())
    pleteOnTimeout("Timeout", 1, TimeUnit.SECONDS);

03、如何使用Completablefuture实现调用接口的时候有超时控制机制,如果接口超时就取消执行任务的线程?

这个题本质上还是在考察我们对Completablefuture相关方法的熟练程度。设计这个功能其实并不困难。

从逻辑的角度讲,应该是设置超时时间,当接口调用超时之后就抛异常。之后我们在catch中手动的捕捉这个异常并且中断相关的线程。

而基于Completablefuture的相关方法,具体的代码设计为:先通过orTimeout方法来实现。如果接口调用超时,可以通过cancel方法来中断执行任务的线程。

代码语言:javascript代码运行次数:0运行复制
public class CompletableFutureTimeoutExample {
    public static void main(String[] args) {
        // 创建CompletableFuture来执行异步任务
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            // 模拟接口调用
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
            return"Response from the server";
        });

        // 设置超时时间
        CompletableFuture<String> timeoutFuture = future.orTimeout(3, TimeUnit.SECONDS);

        try {
            // 获取结果,如果超时会抛出TimeoutException
            String result = timeoutFuture.get();
            System.out.println("Result: " + result);
        } catch (TimeoutException e) {
            System.out.println("Timeout occurred, interrupting the task.");
            // 取消任务,中断线程
            future.cancel(true);
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    }
}

04、Completablefuture是如何实现带有超时时间的等待任务的?

前面我们说过Completablefuture内部会有一个volatile修饰的result字段存储计算结果或异常。在使用get方法的时候,先对result进行判断。如果result此时为空,就说明对应的异步任务还没有执行,调用timeGet方法进行后续操作。

代码语言:javascript代码运行次数:0运行复制
    public T get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException {
        
        //超时时间(我们输入的是秒,在这里要转化成为纳秒)
        long nanos = unit.toNanos(timeout);
        Object r;
        //如果这个异步任务还没有执行,调用timeGet进行执行
        if ((r = result) == null)
            r = timedGet(nanos);
        return (T) reportGet(r);
    }

这个方法当对应的completablefuture任务还没有执行的时候,就会使用timeget方法。

那我们先不着急看timeGet的源码,先自己理一理timeGet的逻辑:

get方法的意思是超时等待。那既然result结果为空就要进入timeGet方法进行超时等待了。所以timeGet方法的大致逻辑应该就是一直等待对应的异步任务有执行结果(result不为空)或者等待超时。让我们来看源码:

首先判断当前的线程有没有被中断,如果你都被中断了就直接返回null。

之后再看传递过来的时间参数是否异常,不能小于或者等于0。如果小于或者等于0的话直接抛出超时异常:

让我们继续看如果传递的参数是正常的逻辑,先对传递过来的超时参数进行了处理,让其加上当前的纳秒数。获取到一个以纳秒为单位的截止时间。

之后为了取得有效时间,又进行了一次判断。如果d==0的话,就给他赋值1纳秒。

并且我们创建了一个等待节点Singnaller,封装当前线程、剩余超时时间和截止时间。

之后就开始死等了,外层就是一个while循环,循环结束的条件是result不为空:

让我们开始看内部的代码,首先当对应的等待节点q为空的时候就开始封装对应的等待节点,添加了封装当前线程、剩余超时时间和截止时间。

这里其实是一个优化后的阻塞操作。当我们封装好了对应的q节点之后,最简单的操作就是直接挂起对应的线程,等待任务的执行。

但如果当前的线程是ForkJoinWorkerThread(FixedThreadPool的工作线程)

的时候,为了避免大量的工作线程都被阻塞而导致任务阻塞的情况,我们就使用helpAsyncBlocker方法来让线程在等待期间协作执行其他任务,避免完全挂起,从而提升资源利用率。

问题来了:为啥这里要单独对ForkJoinWorkerThread这个线程做优化呢?

原因是因为Completablefuture的场景本来就适合异步任务的编排。而如果未手动指定线程池,所有所有 supplyAsyncthenApply 等操作默认由 ForkJoinPoolForkJoinWorkerThread 执行。

代码语言:javascript代码运行次数:0运行复制
CompletableFuture.supplyAsync(() -> doWork())
    .thenAccept(result -> {
        CompletableFuture<Void> nestedFuture = CompletableFuture.runAsync(() -> {
            // 另一个耗时操作
            processResult(result);
        });

        try {
            // 在回调中使用带超时的 get()
            nestedFuture.get(3, TimeUnit.SECONDS); 
        } catch (TimeoutException e) {
            System.out.println("嵌套任务超时!");
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    });

因此我们要单独对ForkJoinPool的线程进行优化。但需要注意的是:Completablefuture只有在两核以及两核以上的服务器中使用ForkJoinPool,如果不符合这个条件,它使用的是:ThreadPerTaskExecutor。(这个后面会详细说

让我们继续回到代码:

在做了优化之后,将对应的的等待节点压入栈中,并且进行超时检查:

之后开始阻塞当前的线程:

阻塞的时候使用的是managedBlock这个方法,在这里我们只关注这个block方法:

由于我们传递的是等待节点Singnaller,所以他在这里实际上调用的是等待节点Singnaller的block方法:

这一看太熟悉了,这不是park方法吗?如果没有超时时间就是用park,如果有超时时间就是使用带有超时时间的parkNanos。

而timedget的后面那些就是进行一些删除操作,清除掉已经超时的无效等待节点之类的操作:

所以说Completablefuture带有超时时间的get的底层设计思路就是:如果当前线程是forkJoinPool的线程,就使用helpAsyncBlocker来进行挂起并优化。

如果是普通线程的话,就先将其加入到栈中,然后直接使用带有超时时间的park方法将其挂起。

最后再清除一些栈中的无效节点,做一下内存优化工作。

05、在上面的问题中,你提到了Completablefuture的线程池,你能详细的讲一讲吗?

从代码中我们可以看出它内部的线程池一共有两种:forkJoinPool和threadPerTaskExecutor。

我们可以看到:默认线程池是根据USE_COMMON_POOL这个布尔值来进行选择的。

  • 如果 USE_COMMON_POOLtrue,则 ASYNC_POOL 被设置为 ForkJoinPoolmonPool()
  • 如果 USE_COMMON_POOLfalse,则 ASYNC_POOL 被设置为一个 ThreadPerTaskExecutor

[ThreadPerTaskExecutor 是一个特殊的线程池实现,其核心设计思想是为每个提交的任务创建一个独立的线程来执行。这种设计特别适合轻量级任务的执行,理论上讲可以创建无数个线程,但实际还是受限于系统资源。]

那么USE_COMMON_POOL是如何进行取值的呢?

它使用的是getCommonPoolPrarllelism来进行的判断,这个方法返回公共线程池的并行度,默认情况下,公共线程池的并行度等于系统的可用处理器数量。

默认情况下,ForkJoinPoolmonPool 的并行度等于系统的可用处理器数量减去1。

  • 假设你的系统有 4 个核心,ForkJoinPool.getCommonPoolParallelism() 返回 3,那么 USE_COMMON_POOL 将被设置为 true,此时的默认线程池为ForkJoinPool。
  • 假设你的系统有两个核心,ForkJoinPool.getCommonPoolParallelism() 返回 1,那么 USE_COMMON_POOL 将被设置为 false,此时的默认线程池为:ThreadPerTaskExecutor 。

也就是说CompletableFuture的默认线程池只有在双核以上的机器内才会使用。在双核及以下的机器中,会为每个任务创建一个新线程,等于没有使用线程池,且有资源耗尽的风险。

而且吧,就算是ForkJoinPool也会有很多槽点,因此在使用Complefuturetable的时候,强烈推荐使用自定义线程池。

终于讲完了,差点累死我。这些内容足以应对90%的Completablefuture面试题了。我也极力推荐大家多去看一看源码,对个人的能力提升还是挺大的。

今天的文章就聊到这里了,相信通过我的介绍,你已经大致了解了这些常问的面试题。希望我的文章可以帮到你。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。原始发表:2025-03-14,如有侵权请联系 cloudcommunity@tencent 删除异步异常工具类线程线程池

本文标签: 大厂常用工具类Completablefuture的面试题都有哪些这次我都替你收集好了