返回
2026.07.25 ARTICLE

Java 虚拟线程落地指南:从能运行到适合生产

理解 Java 虚拟线程适合解决的问题、常见误区,以及在 Spring Boot 服务中的生产落地方式。

虚拟线程最容易被误解成“更快的线程”。准确地说,它解决的是传统平台线程成本较高的问题,让一个进程能够承载大量以等待为主的并发任务。它不会让 CPU 密集型计算凭空提速,也不会替我们消除数据库连接数、下游限流和共享资源竞争。

为什么传统线程容易成为瓶颈

在典型的 Web 服务中,一个请求往往要等待数据库、Redis 或第三方接口。传统“一请求一线程”模型简单直观,但平台线程与操作系统线程关系紧密,数量增加后会带来更高的内存占用和上下文切换成本。

异步编程能提高资源利用率,却会引入回调、响应式操作符和上下文传播等额外复杂度。虚拟线程保留了同步代码的写法:任务阻塞时,JVM 可以暂时卸载承载它的平台线程,让平台线程继续执行其他任务。

一个最小示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import java.util.concurrent.Executors;

public class VirtualThreadDemo {

/**
* 使用每任务一个虚拟线程的执行器处理相互独立的 I/O 任务。
*/
public static void main(String[] args) throws Exception {
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
for (int i = 0; i < 10_000; i++) {
int taskId = i;
executor.submit(() -> fetchRemoteData(taskId));
}
}
}

/**
* 模拟远程调用。真实项目中应设置连接、读取和整体请求超时。
*/
private static String fetchRemoteData(int taskId) {
try {
Thread.sleep(100);
return "task-" + taskId;
} catch (InterruptedException exception) {
Thread.currentThread().interrupt();
throw new IllegalStateException("任务被中断", exception);
}
}
}

这里创建一万个任务并不等于系统可以无条件承受一万个外部请求。虚拟线程降低的是线程成本,不是下游资源成本。

生产环境必须保留并发边界

数据库连接池只有 50 个连接时,同时发起 5000 次查询,只会让大量任务排队。更危险的是,下游接口可能在突发流量下被直接压垮。因此,应在稀缺资源前设置明确的并发上限。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import java.util.concurrent.Semaphore;

public final class LimitedRemoteClient {

private final Semaphore permits = new Semaphore(100);

/**
* 将远程调用并发限制为 100,防止虚拟线程数量失控后冲击下游服务。
*/
public String call() {
boolean acquired = false;
try {
permits.acquire();
acquired = true;
return invokeRemoteService();
} catch (InterruptedException exception) {
Thread.currentThread().interrupt();
throw new IllegalStateException("远程调用被中断", exception);
} finally {
if (acquired) {
permits.release();
}
}
}

/**
* 执行实际远程请求,示例省略 HTTP 客户端细节。
*/
private String invokeRemoteService() {
return "ok";
}
}

警惕线程固定

虚拟线程在某些场景下无法从平台线程卸载,这通常被称为 pinning。长期持有 synchronized 锁并在锁内执行阻塞 I/O,是需要重点排查的模式。应缩短临界区,避免在锁中调用数据库或远程接口,并通过 JFR 观察虚拟线程固定事件。

ThreadLocal 也需要谨慎。虚拟线程可以很多,如果每个线程都保存大对象,内存仍会快速增长。请求上下文应尽量小,并在生命周期结束后清理。

什么时候值得使用

虚拟线程适合大量相互独立、以阻塞 I/O 为主、希望继续采用同步编程模型的任务。对于纯计算任务,线程数通常仍应接近 CPU 核数;对于已经稳定运行的响应式系统,也没有必要仅为了追逐新特性而重写。

上线前至少压测三组指标:吞吐量与尾延迟、堆内存与平台线程数量、数据库和下游接口的排队情况。真正可靠的结论来自系统整体,而不是“创建了多少虚拟线程”。

总结

虚拟线程让并发代码重新变得直接,但它不是无限并发许可证。将它用于 I/O 密集任务,保留连接池、信号量、超时和限流等边界,再配合 JFR 与压力测试验证,才是从“能运行”走向“适合生产”的关键。