开启虚拟线程(Virtual Thread)
一、背景说明
自 Java 21 起,虚拟线程(Virtual Thread) 正式成为稳定特性(JEP 444),为高并发场景提供了一种低成本、高并发、阻塞友好的线程模型。
虚拟线程由 JVM 调度,不再与操作系统线程一一对应,在面对大量阻塞型任务(如数据库、RPC、文件 IO)时,能够显著降低线程创建、上下文切换以及内存占用成本。
t-io / tio-boot 作为高性能网络通信框架,在 Java 21 环境下,可以通过自定义 ThreadFactory 或 ExecutorService 的方式,让业务处理逻辑运行在虚拟线程之上,从而显著提升并发能力和资源利用率。
二、tio-core 中的线程模型说明
在 tio-core 中,线程大致可以分为以下三类(非常重要):
1. smart-common 线程(事件循环线程)
固定 2 个平台线程
启动并长期运行:
commonWorkerwriteWorker
主要职责:
处理 Accept / Connect / Write 事件
触发:
doAccept()doWrite()connect runnable.run()- 少量“同步 read”分支中的
doRead()
说明:
- 这是一组 Selector / Reactor 事件循环线程
- 长期常驻运行(
while(select)) - 对延迟和稳定性要求极高
- 不适合承载业务逻辑
2. readWorkers(Read 事件循环线程)
数量可配置
默认用于:
- 处理 Read 就绪事件
- 数据读取
- 协议解码(ByteBuffer → Packet)
- 在未设置
bizExecutor时,直接执行 handler
特点:
- 仍然是 事件循环线程
- 不是 per-task 模型
- 数量通常与 CPU 核数相关
- 可通过
setWorkThreadFactory+setWorkThreadNum配置
3. 业务执行线程(BizExecutor)
默认未启用
当未设置
bizExecutor时:- handler 由
readWorkers线程直接执行
- handler 由
当设置
bizExecutor后:- handler 会被投递到业务线程池执行
- readWorkers 只负责 decode + 分发
说明:
这是 最适合使用虚拟线程的一层
可以是:
- 平台线程池(Java 8)
- 虚拟线程 Executor(Java 21)
补充说明(重要实测结论)
实测发现: 在返回纯文本、业务逻辑极轻且无阻塞的场景下,不设置
bizExecutor,直接由 readWorkers 执行 handler,性能确实非常高。
但需要注意:
该结论 仅适用于:
- handler 不阻塞
- handler 极短
- 不访问数据库 / RPC / 文件 IO
一旦 handler 出现阻塞或长尾,请务必启用
bizExecutor
三、Java 版本要求
必须使用 Java 21 或以上版本
原因:
- Java 19 / 20 中虚拟线程仍为预览特性
- Java 21 起虚拟线程正式 GA
Thread.ofVirtual()、Executors.newThreadPerTaskExecutor(...)在 Java 21 中稳定可用
可通过以下命令确认版本:
java -version
四、示例代码(tio-boot 中启用虚拟线程)
下面是一个在 tio-boot 中启用虚拟线程作为业务执行线程的最小示例。
package com.litongjava.tio.boot.benchmarker;
import java.lang.Thread.Builder.OfVirtual;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import com.litongjava.tio.boot.TioApplication;
import com.litongjava.tio.boot.benchmarker.config.BenchMarkerAppCconfig;
import com.litongjava.tio.boot.server.TioBootServer;
public class BenchMarkerApp {
public static void main(String[] args) {
long start = System.currentTimeMillis();
// 1. 虚拟线程工厂(用于 work 线程)
OfVirtual ofVirtual = Thread.ofVirtual();
ThreadFactory workTf = ofVirtual.name("t-io-v", 1).factory();
// 2. 设置 readWorkers 使用的线程工厂
TioBootServer server = TioBootServer.me();
server.setWorkThreadFactory(workTf);
server.setWorkThreadNum(Runtime.getRuntime().availableProcessors() * 8);
// 3. 创建业务虚拟线程 Executor(每任务一个虚拟线程)
ThreadFactory bizTf = Thread.ofVirtual().name("t-biz-v", 0).factory();
ExecutorService bizExecutor = Executors.newThreadPerTaskExecutor(bizTf);
server.setBizExecutor(bizExecutor);
TioApplication.run(BenchMarkerApp.class, new BenchMarkerAppCconfig(), args);
long end = System.currentTimeMillis();
System.out.println((end - start) + "ms");
}
}
五、重要说明(非常关键)
1. tio-boot 并非所有线程都是虚拟线程
需要特别注意:
tio-boot 在处理“IO 就绪事件(SelectionKey)”时,使用的仍然是平台线程,而不是虚拟线程。
具体如下:
IO / Reactor 事件循环线程
- Selector / Reactor 线程
- smart-common
- readWorkers
- 全部为平台线程
- 数量有限
- 与 CPU 核数强相关
业务处理线程
- 通过
setBizExecutor(...)注入 - 可以使用 虚拟线程
- 数量不再受 OS 线程数限制
这是一个标准的 Reactor + Worker 架构,也是当前虚拟线程在网络框架中的最佳实践模型。
2. 为什么不把 IO 线程改成虚拟线程?
原因包括:
Selector / Reactor 是:
- 常驻事件循环
- 非 per-task 模型
虚拟线程最适合:
- 阻塞式业务逻辑
- JDBC / Redis / RPC
- 文件 IO
Reactor 线程数量本身很少,通常不是性能瓶颈
因此,将 虚拟线程用于业务层,而不是 IO 事件循环层,是当前最合理、最稳定的设计。
六、适用场景分析
启用虚拟线程后,tio-boot 非常适合以下场景:
高并发长连接
- WebSocket
- TCP
阻塞型业务逻辑
- JDBC
- Redis
- HTTP / RPC 调用
Benchmark / 压测场景
单请求逻辑不复杂,但并发量极高的服务端应用
七、总结
- Java 21 起,虚拟线程可安全用于生产环境
- tio-boot 支持通过
ThreadFactory/ExecutorService注入虚拟线程 - 业务处理线程非常适合使用虚拟线程
- IO / Reactor 事件循环线程仍应使用平台线程
- 这种混合模型在性能、稳定性与可扩展性之间取得了良好平衡
