回顾 Java 传统线程模型的瓶颈及 Project Loom 的设计动机,接着深入剖析虚拟线程的原理、使用方式及调度机制,并结合多种高并发场景给出实战示例。
在传统 Java 并发模型中,每个 java.lang.Thread
都映射到一个操作系统线程(Platform Thread),线程数受限于 OS 资源,且单个线程阻塞时会占用整个 OS 线程,导致大并发场景下资源浪费和性能瓶颈。为了解决这一问题,OpenJDK 社区发起了 Project Loom,引入“虚拟线程”(Virtual Threads),以 JVM 层面的轻量级线程来实现大规模并发。
虚拟线程是一种由 JVM 调度的轻量级线程类型,不再与 OS 线程一一对应,而是通过“载体线程”(Carrier Threads)复用底层操作系统线程资源。当虚拟线程被挂起(如遇到阻塞 I/O),它会释放载体线程,使该载体线程可以继续执行其他虚拟线程,从而极大地提升并发度和资源利用率。
JVM 内部维护一组载体线程(Carrier Threads),用于实际执行虚拟线程的任务。虚拟线程在准备执行时“挂载”到某个载体线程,执行完毕或遇阻塞时“卸载”挂回队列,载体线程可立即切换到其他挂起的虚拟线程。
虚拟线程遇到阻塞调用(如网络 I/O、文件读写)时,JVM 会自动将该虚拟线程解除挂载,释放载体线程资源;待 I/O 就绪后,再次挂载并继续执行,用户无需显式使用异步回调或框架,从而简化编程模型。
在 pom.xml
或编译命令中加入 --enable-preview
并指定模块:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<release>21</release>
<compilerArgs>
<arg>--enable-preview</arg>
<arg>--add-modules</arg>
<arg>jdk.incubator.concurrent</arg>
</compilerArgs>
</configuration>
</plugin>
同时在运行时添加 --enable-preview
参数。
Thread vThread = Thread.ofVirtual().start(() -> {
// 并发任务逻辑
});
vThread.join();
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
List<Future<String>> futures = IntStream.range(0, 1000)
.mapToObj(i -> executor.submit(() -> fetchData(i)))
.collect(Collectors.toList());
for (Future<String> f : futures) {
System.out.println(f.get());
}
}
以上两种方式均可在短代码路径内创建成千上万的虚拟线程。
使用 Java 内置的 HttpServer
,采用虚拟线程处理每个连接:
HttpServer server = HttpServer.create(new InetSocketAddress(8080), 0);
server.createContext("/", exchange -> {
Thread.sleep(10); // 模拟阻塞
byte[] resp = "Hello, Loom!".getBytes();
exchange.sendResponseHeaders(200, resp.length);
exchange.getResponseBody().write(resp);
exchange.close();
});
server.setExecutor(Executors.newVirtualThreadPerTaskExecutor());
server.start();
在同一台 8 核机器上,虚拟线程模式下可轻松处理百万级并发连接,而平台线程模式往往在几万并发后 OOM。
对于每个请求新建虚拟线程,自由开闭 JDBC 连接:
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
executor.submit(() -> {
try (Connection conn = ds.getConnection();
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM users")) {
while (rs.next()) {
// 处理数据
}
}
});
}
虚拟线程释放载体线程时,底层 JDBC 驱动的阻塞调用不再阻塞主线程池,显著提升吞吐。
Reactor/Netty 等异步框架需使用回调或响应式流,链式编程模型较难调试;虚拟线程可保留同步编程风格,且无显式回调,易读易维护。
在高并发场景中,可结合 Semaphore
、限流框架(如 Resilience4j)对虚拟线程并发量进行控制,防止依赖系统资源(数据库、API)过载。例如:
Semaphore semaphore = new Semaphore(100);
Executors.newVirtualThreadPerTaskExecutor().submit(() -> {
if (semaphore.tryAcquire()) {
try {
// 业务处理
} finally {
semaphore.release();
}
} else {
// 拒绝或降级逻辑
}
});
Spring Boot 3.2+ 可通过配置 TaskExecutor
使用虚拟线程:
@Bean
public TaskExecutor taskExecutor() {
return new ConcurrentTaskExecutor(Executors.newVirtualThreadPerTaskExecutor());
}
对 @Async
方法、WebFlux 等均可无缝支持虚拟线程。
Quarkus 通过 @Blocking
注解结合虚拟线程执行阻塞操作,并可在 application.properties
中开启:
quarkus.virtual-threads.enabled=true
无需额外代码,即可获得虚拟线程优势。
ThreadLocal
,推荐使用 Scoped Values(JEP 464)管理上下文。jstack
可查看虚拟线程状态;注意开启 -Djfr
支持。