You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何CompletableFuture.get()被Thread.sleep()阻塞?队列大小影响超时概率

关于CompletableFuture.get()阻塞与LinkedBlockingQueue队列大小影响超时的问题解析

先看你的测试代码:

import com.google.common.collect.Queues;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.Supplier;

public class TaskSleepTest {

    private static final ThreadPoolExecutor EXECUTOR = new ThreadPoolExecutor(4, 16, 1, TimeUnit.MINUTES, Queues.newLinkedBlockingQueue(10));

    public static void main(String[] args) {
        for (int i = 0; i < 30; i++) {
            new Thread(() -> {
                for (int i1 = 0; i1 < 2000; i1++) {
                    Supplier<Void> task = () -> {
                        try {
                            TimeUnit.MILLISECONDS.sleep(200);
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                        return null;
                    };
                    testSleep(task);
                }
            }).start();
        }
    }

    private static void testSleep(Supplier<Void> task) {
        long start = System.currentTimeMillis();
        try {
            CompletableFuture.supplyAsync(task, EXECUTOR).whenComplete((t, throwable) -> {
                if (throwable != null) {
                    System.out.println("error");
                }
            }).get(1000, TimeUnit.MILLISECONDS);
            long end = System.currentTimeMillis();
            System.out.println("success ----------" + Thread.currentThread().getName() + ":" + (end - start));
        } catch (TimeoutException ex) {
            long end = System.currentTimeMillis();
            System.out.println("TimeoutException ----------" + Thread.currentThread().getName() + ":" + (end - start));
        } catch (Exception ex) {
            long end = System.currentTimeMillis();
            System.out.println("Exception ----------" + Thread.currentThread().getName() + ":" + (end - start));
        }
    }
}

一、为什么CompletableFuture.get()会被Thread.sleep()阻塞?

CompletableFuture.supplyAsync(task, EXECUTOR)是把任务提交到指定线程池执行,任务里的TimeUnit.MILLISECONDS.sleep(200)是在线程池的工作线程中休眠,这属于任务执行的必要环节,任务必须等休眠结束才算完成。

而get(1000, TimeUnit.MILLISECONDS)是在调用线程(也就是main方法中启动的30个线程)中阻塞等待任务执行完成,只有当工作线程把任务执行完(包括休眠的200ms),get()才能拿到结果并解除阻塞。本质上是get()在等待任务的总执行时间,任务里的sleep直接拉长了执行时长,导致get()被阻塞。

二、LinkedBlockingQueue队列大小为什么会影响TimeoutException的出现概率?

这要结合ThreadPoolExecutor的任务调度逻辑来看,你的线程池配置是:核心线程数4,最大线程数16,存活时间1分钟,队列是LinkedBlockingQueue。线程池处理任务的优先级顺序是:

  1. 优先用核心线程执行任务,直到核心线程全部被占用;
  2. 核心线程满了之后,新任务会被放入队列等待;
  3. 队列满了之后,才会创建非核心线程,直到达到最大线程数16;
  4. 队列和最大线程数都满了,才会触发拒绝策略。

当队列大小小于16(比如原配置的10):

核心线程4个被占满后,任务会先放进队列,队列最多放10个,此时核心线程+队列任务数=14,再来新任务时队列已经满了,线程池会立刻创建非核心线程,直到达到最大16个工作线程。

16个工作线程可以并行处理任务,每个任务耗时200ms,每秒能处理约80个任务,足以应对你30个线程的提交压力,任务几乎不需要排队就能被执行,总耗时在200-300ms内,远小于get()设置的1000ms超时时间,所以不会抛出TimeoutException。

当队列大小调整为30:

核心线程4个满了之后,新任务会先放进队列,队列可以容纳30个任务,此时核心线程+队列任务数=34,远大于最大线程数16,线程池不会创建任何非核心线程,所有额外任务都积压在队列里排队。

大量任务在队列中等待执行的时间,加上任务本身的200ms执行时间,很容易超过get()设置的1000ms超时阈值,因此会频繁出现TimeoutException。

内容的提问来源于stack exchange,提问作者jason wu

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 20:37:39