贴吧式
高并发后端该选哪种并发模型:线程池、虚拟线程还是事件循环,到底怎么取舍
帖子正文与楼层回复。
楼主 林清和-821
高并发虚拟线程线程池事件循环Java性能调优
背景:最近带团队做服务改造,一个对外接口 QPS 800+,里面要串行调 4 个下游 RPC、一次 DB、一次 Redis,P50 还行但 P99 经常抖到 300ms+。我翻了下现有代码,发现「并发模型」这块其实是一笔糊涂账:有的地方用 ForkJoinPool 共享池跑阻塞 IO,有的地方手搓 ThreadPoolExecutor 每个接口一个池,还有的新同学直接上 CompletableFuture 链式调用,线程池参数全凭感觉。想借这个帖把「Java 生态里线程池 / 虚拟线程 / 事件循环(Reactor、Netty 这类)到底怎么选」摊开聊,因为这个问题我踩了 5 年,每次重构都会重新纠结一遍。
常见分歧 / 几种做法,我观察下来大概分四派:
- 保守派:所有阻塞调用都丢线程池,每个资源一个池,参数按 CPU 核数 * 10 拍脑袋。优点是心智简单、栈深、好调试;缺点是池子一多,线程数爆炸,上下文切换和内存都是成本,而且「每个接口一个池」会让故障互相传染。
- 虚拟线程派(Loom):Java 21 之后直接用 virtual thread,阻塞 IO 直接写同步代码,框架层面挂起。优点是把「写同步代码 + 获得异步吞吐」这件事统一到一处,对老代码改造成本极低;缺点是对「pinning」敏感——synchronized 块、JNI 调用会卡住载体线程,JDK 版本和依赖库是否兼容要逐个验证,而且池子治理那套经验(拒绝策略、队列长度监控)大部分失效了。
- 事件循环派:Reactor / RxJava / Netty 单线程事件循环,所有 IO 非阻塞。吞吐上限最高、线程数最少;但代码可读性、异常传播、调试栈全是噩梦,团队里只要有一个新同学,维护成本就会指数上升。
- 混合派:网关和纯 IO 边界用事件循环,业务逻辑层用线程池或虚拟线程,中间用「线程上下文透传」桥接。灵活但复杂度最高,桥接层自己就会出 bug。
我的倾向:在「业务逻辑里大量阻塞 IO、团队 5 人以上、JDK ≥ 21」的前提下,我会优先选虚拟线程 + 少量专用池(CPU 密集算子才用平台线程池),原因是它把「写起来像同步、跑起来像异步」这件事的边际成本降到了最低,而且对老代码改造是渐进式的,不用一次性重写。事件循环我留给网关 / 长连接 / 纯转发这类「IO 占比 > 90%」的场景,业务层不轻易碰。线程池我保留给 CPU 密集计算和第三方阻塞 SDK 的隔离。但我承认这个结论强依赖 JDK 版本和依赖库兼容性,如果是 JDK 8 / 11 或者大量 C 库调用,虚拟线程的优势会大打折扣,那时事件循环或成熟线程池反而更稳。
想听大家聊聊几个具体问题:
- 你们生产环境里,虚拟线程和平台线程池是怎么划界的?有没有「看着能用,压测一上量就崩」的坑?
- synchronized 导致的 pinning 你们是怎么治理的?是改 ReentrantLock、还是直接禁用虚拟线程?
- 事件循环和线程池混用的桥接层(线程上下文透传、异常传播),你们是用框架现成的还是自己封的?踩过什么坑?
- 如果是 JDK 8 / 11 的老系统,你们会怎么选?是硬上虚拟线程(升级 JDK)还是老老实实事件循环?
欢迎把具体场景(QPS、下游数量、JDK 版本、团队规模)一起带上,我按场景对比会更准。
赞 0 · 收藏 0 · 楼层 0
楼层
#1
作者
楼层正文
还没有人回复。