扛住高并发:Java21虚拟线程与异步编程的实战

admin 2026-07-19 04:38:27 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文以AI网络安全平台为案例,阐述Java21虚拟线程解决高并发与长耗时矛盾的核心价值。虚拟线程通过轻量级调度,使阻塞不浪费平台线程,配合SSE流式输出提升体验。文章还介绍了事务异步分离、连接池优化等工程实践,并指出虚拟线程需避免synchronized钉死和ThreadLocal滥用等陷阱,强调持续迭代的工程方法论。 综合评分: 87 文章分类: 实战经验,安全建设,解决方案


cover_image

扛住高并发:Java 21 虚拟线程与异步编程的实战

河北镌远 河北镌远

河北镌远网络科技有限公司

2026年7月17日 15:00 河北

在小说阅读器读本章

去阅读

前四篇我们讲了架构、RAG、策略优化、安全合规,这一篇我们回到一个最「硬核」、也最能体现技术选型眼光的主题——高并发。AI 应用有一个和传统 Web 应用截然不同的特征:单个请求的处理时间极长。一次大模型推理,动辄几十秒甚至上分钟。这个特征,会让传统的线程模型在并发面前迅速崩溃。而平台选择 Java 21、用上虚拟线程,正是为了从根上解决这个问题。

一、问题的本质:长耗时 + 高并发的矛盾

我们先把问题讲清楚。传统的 Java Web 服务器(比如 Tomcat、Undertow),处理请求用的是「平台线程」——也就是操作系统的内核线程。这种线程有两个特点:创建成本高、数量有限。一台服务器通常只能开几百到一两千个平台线程,再多,内存和上下文切换的开销就会把系统拖垮。

在传统 Web 场景下,这不是问题——因为每个请求处理得很快(几毫秒到几十毫秒),线程用完很快就释放,可以服务下一个请求。一两千个线程足以支撑上万的 QPS。但 AI 场景完全不同:一个请求要等大模型推理几十秒,在这几十秒里,处理它的那个平台线程就一直被占着、什么也干不了(它在等模型返回,纯阻塞)。

算一笔账:如果有 1000 个平台线程,每个请求要阻塞 30 秒,那么这台服务器同时最多只能处理 1000 个请求。第 1001 个用户,就只能排队干等。对一个要服务大量并发的 AI 平台来说,这是致命的瓶颈。

二、虚拟线程:用「廉价线程」破局

Java 21 正式落地的 虚拟线程(Virtual Thread),是 Project Loom 项目多年打磨的成果,专门用来解决这类问题。它的核心思想可以一句话概括:

虚拟线程是「廉价」的。你可以轻松创建成千上万、甚至上百万个虚拟线程,而它们底层只由少量平台线程来承载。当一个虚拟线程阻塞(比如在等 AI 返回)时,它会自动「让出」底层的平台线程,让平台线程去服务其他虚拟线程。

这个机制的妙处在于:阻塞不再浪费宝贵的平台线程。还是上面那笔账——用虚拟线程,1000 个平台线程可以承载几万、几十万个虚拟线程,因为那些在等 AI 返回的虚拟线程,根本不占用平台线程。于是「同时处理的请求数」从受限于平台线程数,变成了几乎只受限于内存。下面这张图说明了这一点。

图 1:虚拟线程调度模型——少量平台线程承载海量虚拟线程

代码层面有多简单

虚拟线程最让人惊喜的,是它的编程模型几乎没有变化。过去要解决阻塞问题,得用复杂的异步、回调、响应式编程(Reactor、CompletableFuture 嵌套),代码写起来又绕又难维护。而虚拟线程让你 用写同步代码的方式,获得异步的性能。在平台里,启动一个虚拟线程来处理 AI 流式对话,就这么简单:

  AiChatServiceImpl.java · 虚拟线程启动

// 用虚拟线程处理 AI 流式对话

Thread.ofVirtual().start(() -> {

    try {

        doStreamChat(bo, emitter, terminateFlag, currentUser);

    } finally {

        // 清理资源

    }

});

// 即使内部有大量阻塞等待(等模型逐字返回),

// 也不会占用宝贵的平台线程

Thread.ofVirtual().start(...)——一行代码,就启动了一个虚拟线程。里面的 doStreamChat 可以放心地写成阻塞式的同步逻辑,完全不需要操心异步编排。这就是虚拟线程的最大价值:把高并发的复杂度,从应用代码下沉到了 JVM 运行时

三、SSE 流式输出:让等待「可感知」

虚拟线程解决了「能同时处理多少请求」,但还有一个体验问题:用户不愿意盯着一个加载圈干等几十秒。解决方案是SSE(Server-Sent Events)流式输出——让 AI 的回答像打字机一样逐字吐出,用户在第一个字出现时就知道「系统在工作了」。

平台的流式处理把虚拟线程和 SSE 优雅地结合在一起。当一个流式对话请求到来时,系统会注册一个 SSE 连接,启动一个心跳保活机制(每 30 秒发一次心跳,防止连接被中间设备掐断),然后用虚拟线程去执行实际的流式推理,把模型逐字返回的内容实时推送给前端。整个过程,主线程不被阻塞,用户体验也流畅。

  AiChatServiceImpl.java · SSE 流式 + 虚拟线程

public void chatWithAiStream(String sessionId, AiChatBo bo, SseEmitter emitter) {

    conversationManager.registerSession(sessionId, emitter);

    // 30 秒心跳保活,防止连接被中间设备断开

    ScheduledFuture<?> heartbeat =

            conversationManager.startHeartbeat(emitter, done);

    // 虚拟线程执行流式推理,不阻塞主线程

    Thread.ofVirtual().start(() ->

            doStreamChat(bo, emitter, terminateFlag, currentUser));

}

推送给前端的事件也做了精心设计:有表示处理进度的 progress 事件(「正在检查输入安全性…」「正在检索知识库…」)、标识对话类型的 type 事件、会话关联的 contextId、错误提示 error,以及任务完成的 done 事件(附带总处理耗时)。这些细粒度的事件,让前端可以呈现出丰富的实时反馈,而不只是干巴巴地等一个最终结果。

四、事务与异步分离:快速响应 + 可靠执行

并发优化的另一个关键战场,是事务管理。这里有一个在 AI 场景里特别突出的矛盾:数据库事务希望「短」,而 POC 执行偏偏「长」

问题:长事务的危害

设想一个漏洞验证请求:它需要先在数据库里保存一条验证记录,然后执行 POC(可能耗时几十秒),最后把结果写回数据库。如果把这整个过程放在一个数据库事务里,那么这个事务会持续几十秒——在这期间,它占用着数据库连接、持有着行锁,极易引发锁等待、连接池耗尽、甚至死锁。长事务是数据库性能的头号杀手。

方案:拆成两段

平台的解法(对应迭代记录 Phase 2 第 28 条)是把它拆开:主事务只负责「快」的部分——保存验证记录、把状态置为 PENDING、立即返回任务 ID 给前端。而「慢」的 POC 执行,则丢给异步线程池去做,执行结果用一个 独立的新事务(REQUIRES_NEW 传播级别)写回。这样主事务在毫秒级就提交了,数据库压力骤降;而 POC 的执行和结果写入,在后台独立完成,互不干扰。

图 2:事务与异步执行分离——主事务快速提交,POC 在独立事务中后台执行

  VerifyServiceImpl.java · 事务异步分离

@Transactional

public VerifyResultVo manualVerify(VerifyManualBo bo) {

    // 主事务:只做快的部分,毫秒级提交

    verifyRecord.setStatus(PENDING);

    verifyRecordMapper.insert(verifyRecord);

    // 慢的 POC 执行丢给异步线程池

    verifyExecutor.execute(() ->

        verifyExecutionService.executeAndSave(verifyRecord.getId()));

    return new VerifyResultVo(verifyRecord.getId());

}

// 结果写入用独立的新事务,不影响主流程

@Transactional(propagation = Propagation.REQUIRES_NEW)

public void executeAndSave(Long recordId) { /* … */ }

顺带一提,平台还把一些散落的 new Thread() 裸用,统一收编进了受管理的线程池(@Async 配合命名线程池,如 aiTaskExecutor)。裸用 new Thread() 在生产环境是大忌——无法控制并发数、无法复用、出问题难以排查。用统一的线程池,才能对并发资源做精细管控。

五、连接池与单例:被忽视的并发基石

高并发不只靠线程模型,底层资源的复用同样关键。平台在这方面做了一组扎实的基础优化。

● 数据库连接池:把 HikariCP 的 maxPoolSize 调到 50、minIdle 设为 10,并开启了连接泄漏检测(60 秒阈值),支撑 200–1200 级别的并发。

● HTTP 连接池:调用 Embedding 模型的 OkHttp 客户端用 static 单例 + 10 连接池,复用 TCP 连接,避免每次请求都三次握手。

● 模型单例缓存:用双重检查锁(DCL)缓存嵌入模型实例,避免重复初始化的巨大开销。

● Milvus 客户端单例:向量库客户端做单例保护,并修复了一处会误关共享客户端的 bug(迭代记录 Phase 2 第 30 条),改为仅在应用关闭时(@PreDestroy)释放。

这些优化单看都很「基础」,但它们是高并发的地基。线程模型决定了「能开多少并发」,而连接池和单例缓存决定了「这些并发能不能拿到资源、跑得快不快」。两者缺一不可。

六、持续演进:工程是一场马拉松

回顾整个平台的优化历程,你会发现它不是一蹴而就的,而是分阶段、持续迭代出来的。从紧急修复,到性能优化,再到数据质量、功能完善,最后到产品化与合规——每一个阶段都解决一类问题。

图 3:平台分阶段持续演进的迭代路线

这张演进图,其实是所有严肃软件工程的缩影:没有哪个系统是一开始就完美的。先让它「能跑」(Phase 0 紧急修复),再让它「跑得快」(Phase 1 性能优化),然后让它「跑得准」(Phase 2 数据质量),接着让它「功能全」(Phase 3 功能完善),最后让它「上得了生产、过得了合规」(Phase 4 产品化)。工程是一场马拉松,而不是百米冲刺。

七、虚拟线程的「坑」:不是无脑替换

     讲了这么多虚拟线程的好,必须泼一盆冷水:虚拟线程不是万能药,更不是「把所有线程池换成虚拟线程」就能躺赢。用不好,反而会踩坑。这部分经验,对真正想上虚拟线程的团队尤其重要。

第一个坑是 synchronized 引起的「线程钉死」(pinning)。虚拟线程的高效,依赖于它阻塞时能「让出」底层平台线程。但如果阻塞发生在 synchronized 同步块里,虚拟线程就无法让出,会把底层平台线程「钉死」住——这就退化回了平台线程的老问题。所以在虚拟线程里,应优先用 ReentrantLock 等显式锁替代 synchronized。

第二个坑是滥用线程局部变量(ThreadLocal)。虚拟线程数量极大,如果每个虚拟线程都往 ThreadLocal 里塞大对象,内存会被迅速吃光。平台在用 ThreadLocal 传递 POC 执行的输出回调时,就特别注意了及时清理,避免内存泄漏。

第三个坑是「虚拟线程不适合 CPU 密集型任务」。虚拟线程的优势在于「阻塞等待」类任务(等网络、等 IO、等 AI 返回),对于纯计算的 CPU 密集任务,它并不会更快——这类任务该用多少平台线程还是多少。理解「虚拟线程优化的是阻塞、不是计算」这一点,才能用对地方。

虚拟线程的正确打开方式:用在「大量并发 + 频繁阻塞等待」的场景(这正是 AI 应用的典型特征),同时避开 synchronized 钉死、ThreadLocal 滥用、CPU 密集任务这几个坑。它是利器,但要用对地方。

八、线程池怎么配:经验值与权衡

即便有了虚拟线程,传统的线程池在很多场景下依然不可或缺——尤其是那些需要「限流」「隔离」的场景。平台里像 POC 执行、知识库处理这类任务,用的就是受管理的命名线程池。线程池的参数怎么配,是个经典话题。

平台为 POC 生成配置了核心线程数 4、最大线程数 8 的专用线程池。这个数字不是拍脑袋来的:POC 生成是「调用 AI + 沙箱执行」的混合任务,并发太高会同时占用大量模型推理资源和容器资源,把整机拖垮,所以要刻意限流;但太低又会让任务排队过久。4 到 8 是结合实测压测得出的平衡点。这也说明一个道理:线程池参数没有放之四海皆准的公式,必须结合任务特性和实测数据来定,IO 密集型可以配大些,CPU 密集型则接近核心数。

● 为不同任务配独立线程池:让 AI 任务、POC 执行、知识库处理互相隔离,一类任务的拥塞不会拖垮另一类。这叫「线程池隔离」,是防止故障扩散的重要手段。

● 给线程池起名字:命名线程池(如 aiTaskExecutor)让日志和排查时一眼能看出线程归属,比一堆 pool-1-thread-3 友好太多。

● 配置拒绝策略:当线程池满载时该排队、丢弃还是阻塞调用方,要根据业务容忍度明确设定,而不是用默认值碰运气。

九、AI 落地垂直领域的方法论

      我们以一个 AI 网络安全平台为样本,完整地拆解了「大模型如何在垂直领域真正落地」。回头看,有几条贯穿始终的方法论值得提炼:

● 专有模型 + RAG + 本地部署:在数据安全、领域精度和幻觉抑制之间取得平衡,这是垂直领域 AI 的黄金组合。

● AI 做建议,工程做保障:AI 提供智能上限,而延迟写入、事务分离、沙箱隔离这些工程设计,才让智能真正可靠落地。

● 安全平台对自己要求更高:纵深防御、失败关闭、持续加固,把安全刻进每一层。

● 拥抱现代技术红利:Java 21 虚拟线程让「高并发 + 长耗时」的 AI 场景,用简单的同步代码就能优雅应对。

● 持续迭代:没有一步到位的完美系统,只有分阶段演进的工程纪律。

如果你也在做 AI 应用的落地,希望这个系列里那些真实的架构取舍、工程细节和踩过的坑,能给你一些可借鉴的参考。大模型的能力固然激动人心,但把这份能力稳稳地、安全地、可规模化地交付到生产环境,依然需要扎实的工程功力。这,或许就是 AI 时代里,工程师不变的价值。感谢一路读到这里,我们后会有期。

附:写给想做 AI 工程的你

做一个 AI 演示(demo)很容易:找个模型、写几行提示词、跑通一个例子,几小时就能出活。但当你要把它变成一个能 7×24 小时服务真实用户、扛得住并发、守得住安全、过得了合规的产品时,你会发现 AI 模型本身只占工作量的一小部分,剩下的全是「传统」的软件工程——数据库优化、并发控制、事务管理、安全加固、监控告警、持续迭代。这些不性感、不上头条,却是决定一个 AI 系统生死的真正关键。

所以我的建议是:如果你想在 AI 时代做出有价值的东西,别只盯着模型,更要练好工程的基本功。虚拟线程怎么用、事务怎么拆、缓存怎么做、安全怎么守——这些「老掉牙」的工程能力,在 AI 时代非但没有过时,反而因为 AI 应用「长耗时、高并发、强安全」的新特征,变得比以往任何时候都更重要。模型的能力日新月异,但把能力稳稳交付的工程智慧,是历久弥新的。

AI 给了我们一双更强大的手,但决定我们能走多远的,依然是脚下那条由工程铺成的路。愿你既能追逐模型的星辰,也能脚踏工程的大地。

免责声明:因传播、利用本公众号“河北镌远网络科技有限公司”所提供信息而产生的任何直接或间接后果及损失,均由使用者本人自行承担,本公众号及作者不承担任何责任。本公众号所发表内容中,凡注明来源的,版权归原出处所有;无法查证版权或未注明出处的,均来自网络并系转载,转载旨在传递更多信息,版权归原作者所有。若存在侵权情况,请联系小编,我们将第一时间删除处理。


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:河北镌远网络科技有限公司 河北镌远 河北镌远《扛住高并发:Java 21 虚拟线程与异步编程的实战》

评论:0   参与:  0