Understanding Java Virtual Threads: Lightweight Concurrency in Modern Java
Understanding Java Virtual Threads: Lightweight Concurrency in Modern Java
理解 Java 虚拟线程:现代 Java 中的轻量级并发
Java 21 introduced one of the most significant changes to the platform’s concurrency model in years: virtual threads, delivered as part of Project Loom (JEP 444). If you’ve ever struggled with thread pool tuning or hit scalability walls with blocking I/O, virtual threads are worth understanding.
Java 21 引入了该平台多年来并发模型中最重大的变革之一:作为 Project Loom (JEP 444) 一部分交付的虚拟线程。如果你曾经为线程池调优而苦恼,或者在处理阻塞式 I/O 时遇到过扩展性瓶颈,那么了解虚拟线程是非常有必要的。
The Problem with Platform Threads
平台线程的问题
Traditional Java threads (now called platform threads) are thin wrappers around operating system threads. They are expensive: Each thread consumes around 1 MB of stack memory by default. The OS scheduler manages context switching, which adds overhead. A typical machine can only support a few thousand concurrent platform threads before running into resource limits. This led to the popularity of asynchronous, reactive programming styles that avoid blocking. But reactive code is notoriously hard to read, debug, and maintain.
传统的 Java 线程(现在称为平台线程)是对操作系统线程的轻量级封装。它们非常昂贵:每个线程默认占用约 1 MB 的栈内存。操作系统调度器负责管理上下文切换,这会带来额外的开销。一台典型的机器在达到资源限制前,通常只能支持几千个并发的平台线程。这导致了旨在避免阻塞的异步响应式编程风格的流行。然而,响应式代码以难以阅读、调试和维护而著称。
Enter Virtual Threads
引入虚拟线程
Virtual threads are lightweight threads managed by the JVM rather than the operating system. Millions of them can run concurrently, and they are mapped onto a small pool of platform threads (called carrier threads). When a virtual thread blocks on I/O, the JVM automatically unmounts it from its carrier thread, freeing that carrier to run other virtual threads.
虚拟线程是由 JVM 而非操作系统管理的轻量级线程。数百万个虚拟线程可以并发运行,它们被映射到一小部分平台线程(称为载体线程)上。当虚拟线程在 I/O 操作上阻塞时,JVM 会自动将其从载体线程上卸载,从而释放该载体线程去运行其他虚拟线程。
Creating Virtual Threads
创建虚拟线程
The API is refreshingly simple: API 的设计非常简洁:
// Start a single virtual thread
Thread.startVirtualThread(() -> {
System.out.println("Running in a virtual thread");
});
// Using a builder
Thread vThread = Thread.ofVirtual()
.name("worker-1")
.start(() -> doWork());
The real power shows up with executors: 真正的威力体现在执行器(Executors)中:
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
IntStream.range(0, 10_000).forEach(i -> {
executor.submit(() -> {
Thread.sleep(Duration.ofSeconds(1));
return i;
});
});
}
This code spawns 10,000 concurrent tasks. With platform threads this would likely exhaust system resources; with virtual threads it runs comfortably.
这段代码启动了 10,000 个并发任务。如果使用平台线程,这很可能会耗尽系统资源;而使用虚拟线程,它能轻松运行。
Writing Simple, Blocking Code Again
再次编写简洁的阻塞式代码
The biggest win is that you can write straightforward, synchronous-looking code that still scales: 最大的优势在于,你可以编写直观、看起来像同步的代码,同时依然具备良好的扩展性:
var response = httpClient.send(request, BodyHandlers.ofString());
process(response.body());
The blocking call no longer wastes a precious OS thread. The scheduler handles the rest.
阻塞调用不再浪费宝贵的操作系统线程,调度器会处理剩下的工作。
Best Practices
最佳实践
- Don’t pool virtual threads. They are cheap to create. Use
newVirtualThreadPerTaskExecutor()instead of a fixed pool. - 不要池化虚拟线程。 它们创建成本很低。请使用
newVirtualThreadPerTaskExecutor()而不是固定大小的线程池。 - Avoid synchronized blocks around blocking I/O. They can pin the virtual thread to its carrier. Prefer
ReentrantLock. - 避免在阻塞式 I/O 周围使用 synchronized 代码块。 这可能会将虚拟线程“固定”在载体线程上。建议优先使用
ReentrantLock。 - Use them for I/O-bound tasks. For CPU-bound work, platform threads still make sense.
- 将它们用于 I/O 密集型任务。 对于 CPU 密集型工作,平台线程仍然是合理的选择。
Conclusion
总结
Virtual threads let you keep the simplicity of blocking code while achieving the scalability once reserved for reactive frameworks. Combined with frameworks like Spring Boot, which now support virtual threads out of the box, they represent a meaningful step forward for Java server-side development. Try them in your next project and measure the difference for yourself.
虚拟线程让你在保持阻塞式代码简洁性的同时,实现了曾经只有响应式框架才能达到的扩展性。结合 Spring Boot 等现已原生支持虚拟线程的框架,它们代表了 Java 服务器端开发向前迈出的重要一步。在你的下一个项目中尝试它们,并亲自衡量其中的差异吧。