惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

云风的 BLOG
云风的 BLOG
The GitHub Blog
The GitHub Blog
A
About on SuperTechFans
P
Proofpoint News Feed
G
Google Developers Blog
Stack Overflow Blog
Stack Overflow Blog
IT之家
IT之家
Microsoft Security Blog
Microsoft Security Blog
F
Fortinet All Blogs
人人都是产品经理
人人都是产品经理
博客园 - 叶小钗
C
Check Point Blog
Microsoft Azure Blog
Microsoft Azure Blog
aimingoo的专栏
aimingoo的专栏
月光博客
月光博客
美团技术团队
D
Docker
博客园 - Franky
Y
Y Combinator Blog
大猫的无限游戏
大猫的无限游戏
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
博客园 - 【当耐特】
罗磊的独立博客
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报

博客园 - 幽州散人

SQL优化案例 synchronized 为啥会让虚拟线程卸载失效 主线任务和使命 温州3日之旅 Snowflake雪花算法与发号器的应用 windows Java 幽灵转发壳进程 也谈MySQL limit offset深翻页问题 SHOW SESSION STATUS 与 Handler 变量 MySQL 5.7 Profiles 执行性能分析 MySQL临时表与文件排序 MySQL 5.7 MRR多范围读优化 MySQL 5.7 ICP索引条件下推优化 MySQL符合索引与最左前缀原则 从I/O 的物理成本理解回表和索引覆盖 第4个电瓶以及补漆 Flink原理:并行度与keyGroup桶 什么是数字批发银行 Java虚拟线程(三)实现原理 Java虚拟线程(二) Java虚拟线程(一) 大模型推理层服务化架构 传奇调查员之路:理智值-99,但我必须听懂深渊的语言 js里调用智能合约读/写函数的方法的区别 字节与其16进制字符表示转换的bug Redisson分布式锁 交易心得 DexScreener接口初探 某安全软件跑飞了。。 Ed25519算法签名与验签的Java实现 Tendermint拜占庭容错引擎
CompletableFuture + 异步Servlet:同步的姿势、异步的效果
幽州散人 · 2026-09-01 · via 博客园 - 幽州散人

这是一个务实但有效的工具。
假设在Servlet线程模型下的web容器,比如SpringBoot web starter下,
要处理一个慢接口,一般是文件操作、公网第三方接口调用等等,除了提高业务逻辑处理速度提高RT之外,
能做的防守动作是至少不要让这个慢接口拖垮Tomcat的worker线程池。

可供选择的方案可能很快是改技术栈为WebFlux,拥抱Netty; 或者升级老旧的JDK版本到21,直接上虚拟线程、肆意挥霍。
但是,WebFlux代码写法会让一些开发人员不习惯,直接上虚拟线程看似最简单,但是一些情况下也是需要付出一定的精力去确认的,比如:
synchronized 重量级锁 使得JVM没法使用yield使虚拟线程从载体线程卸载,导致虚拟线程与载体线程同时被阻塞。
我们需要确保业务逻辑所使用的第三方库中也不能包含这个关键字,排查起来还是需要一些细心的。

CompletableFuture 配合Servlet3.0异步机制,可以将耗时的业务逻辑转为异步任务,但是整个流程又确保是同步过程,可以收到不错的效果。

Controller有如下慢接口

private final SlowExecutorConfigV2 nnSlowExecutor;

@GetMapping("/nnslow")
public CompletableFuture<ApiResult<Long>> nnslow(@RequestParam Long id) {
    return nnSlowExecutor.submit(() -> mpService.slow(id))
            .orTimeout(15, TimeUnit.SECONDS)
            .thenApply(ApiResult::success);
}

其中mpService.slow(id)预估需要执行几秒到十秒不等
我们上面是把这个逻辑封装成Callable丢给一个独立线程池,执行结果以CompletableFuture形式返回。
这背后,spring mvc 在任务提交到独立线程池的时候就释放worker线程回其线程池,由独立的ThreadPoolExecutor执行业务逻辑,
期间spring mvc保持连接,业务逻辑执行完成由实际干活的异步线程往连接中写CompletableFuture封装的结果。

@Component
public class SlowExecutorConfigV2 {

    private static final Logger LOGGER = LoggerFactory.getLogger(SlowExecutorConfigV2.class);

    /** 工作线程数 */
    private static final int MAX_THREADS = 5;
    /** 同时承载的慢任务上限(工作线程 + 队列) */
    private static final int MAX_CONCURRENT = 30;

    private final ExecutorService pool = new ThreadPoolExecutor(
            MAX_THREADS,
            MAX_THREADS,
            60, TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(MAX_CONCURRENT - MAX_THREADS),
            new ThreadFactory() {
                private final AtomicInteger seq = new AtomicInteger(1);

                @Override
                public Thread newThread(Runnable r) {
                    return new Thread(r, "slow-pool-" + seq.getAndIncrement());
                }
            },
            new ThreadPoolExecutor.AbortPolicy());

    /**
     * 提交慢任务,返回 CompletableFuture 由调用方决定后续处理。
     *
     * 行为约定:
     * - 正常完成:future 以任务返回值完成
     * - 任务抛异常:原样转给 future(全局异常处理器记录真实原因)
     * - 队列满被拒绝:future 以 BusinessException("业务繁忙") 完成,快速失败不堆积
     */
    public <T> CompletableFuture<T> submit(Callable<T> task) {
        CompletableFuture<T> future = new CompletableFuture<>();
        try {
            pool.execute(() -> {
                try {
                    future.complete(task.call());
                } catch (Exception e) {
                    future.completeExceptionally(e);
                }
            });
        } catch (RejectedExecutionException e) {
            LOGGER.error("slow 线程池已满,拒绝任务:{}", e);
            future.completeExceptionally(new BusinessException("Slow业务繁忙"));
        }
        return future;
    }

    /** 容器关闭时停止接收新任务,等待已提交任务执行完,避免非 daemon 线程拖住 JVM 退出 */
    @PreDestroy
    public void shutdown() {
        pool.shutdown();
    }
}