9291 字
19 分钟
Java 21 虚拟线程:彻底告别阻塞,高并发应用的终极解决方案
Java 21 虚拟线程:彻底告别阻塞,高并发应用的终极解决方案
摘要
Java 21 引入的虚拟线程(Virtual Threads)是 Java 并发模型的重大革新。不同于传统平台线程,虚拟线程由 JVM 在用户空间管理,可轻松创建数百万个而不会耗尽系统资源。本文将深入解析虚拟线程的工作原理、使用方法,并通过实际案例展示如何将其应用于高并发 Web 服务,实现性能的大幅提升。
一、传统线程模型的痛点
在 Java 21 之前,传统的线程模型存在以下问题:
1.1 资源消耗高
// 传统方式创建线程
for (int i = 0; i < 100000; i++) {
new Thread(() -> {
// 执行任务
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
上述代码尝试创建 10 万个线程,结果会抛出 OutOfMemoryError,因为每个平台线程默认占用约 1MB 的堆栈空间。
1.2 阻塞操作浪费资源
传统线程在执行 I/O 操作(如网络请求、数据库查询)时会阻塞,期间该线程无法执行其他任务,造成 CPU 资源的浪费。
二、虚拟线程:轻量级并发解决方案
2.1 什么是虚拟线程?
虚拟线程是 Java 21 引入的轻量级线程,由 JVM 在用户空间管理,而非操作系统。其主要特点:
| 特性 | 平台线程 | 虚拟线程 |
|---|---|---|
| 创建成本 | 高(约 1MB 栈空间) | 极低(几百字节) |
| 数量限制 | 数千个 | 数百万个 |
| 调度方式 | 操作系统内核 | JVM 用户空间 |
| 阻塞成本 | 高(占用 OS 线程) | 低(自动让出) |
2.2 创建虚拟线程的三种方式
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
public class VirtualThreadDemo {
// 方式一:使用 Thread.ofVirtual()
public static void way1() {
Thread.startVirtualThread(() -> {
System.out.println("Running in virtual thread: " + Thread.currentThread());
});
}
// 方式二:使用 Thread.Builder
public static void way2() {
ThreadFactory factory = Thread.ofVirtual().name("vt-", 0).factory();
Thread vt = factory.newThread(() -> {
System.out.println("Running in virtual thread: " + Thread.currentThread());
});
vt.start();
}
// 方式三:使用 Executors.newVirtualThreadPerTaskExecutor()
public static void way3() throws Exception {
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
for (int i = 0; i < 1000; i++) {
final int taskId = i;
executor.submit(() -> {
System.out.println("Task " + taskId + " running in " + Thread.currentThread());
try {
Thread.sleep(100); // 模拟工作
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
} // 自动等待所有任务完成
}
public static void main(String[] args) throws Exception {
System.out.println("=== Virtual Thread Demo ===");
way1();
way2();
way3();
}
}
三、实战:构建高并发 Web 服务
3.1 使用虚拟线程的 Spring Boot 3.2+ 应用
Spring Boot 3.2 开始原生支持虚拟线程,配置非常简单:
# application.yml
spring:
threads:
virtual:
enabled: true # 启用虚拟线程
server:
tomcat:
threads:
max: 200 # Tomcat 最大线程数(虚拟线程可设置较大)
3.2 高并发控制器示例
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
@RestController
@RequestMapping("/api")
public class HighConcurrencyController {
private final RestTemplate restTemplate = new RestTemplate();
// 模拟 CPU 密集型任务
@GetMapping("/cpu-task/{n}")
public String cpuIntensiveTask(@PathVariable int n) {
long start = System.currentTimeMillis();
long result = fibonacci(n);
long duration = System.currentTimeMillis() - start;
return String.format("Fibonacci(%d) = %d, Time: %dms, Thread: %s",
n, result, duration, Thread.currentThread());
}
// 模拟 I/O 密集型任务(阻塞操作)
@GetMapping("/io-task")
public String ioIntensiveTask() throws InterruptedException {
String threadInfo = Thread.currentThread().toString();
// 模拟数据库查询或 HTTP 调用
Thread.sleep(100); // 100ms 延迟
return String.format("I/O task completed. Thread: %s", threadInfo);
}
// 批量并发请求示例
@GetMapping("/concurrent-batch/{count}")
public String concurrentBatch(@PathVariable int count) {
long start = System.currentTimeMillis();
var futures = java.util.stream.IntStream.range(0, count)
.mapToObj(i -> CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(50); // 模拟工作
return "Task-" + i;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return "Error-" + i;
}
}))
.toList();
// 等待所有任务完成
var results = futures.stream()
.map(CompletableFuture::join)
.toList();
long duration = System.currentTimeMillis() - start;
return String.format("Completed %d tasks in %dms. Sample: %s",
count, duration, results.subList(0, Math.min(5, results.size())));
}
private long fibonacci(int n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
}
3.3 虚拟线程 vs 平台线程性能对比
import java.util.concurrent.*;
import java.time.Duration;
public class ThreadComparison {
public static void main(String[] args) throws Exception {
int taskCount = 100_000; // 10 万任务
int sleepMs = 10; // 每个任务睡眠 10ms
System.out.println("=== Thread Comparison Test ===");
System.out.println("Tasks: " + taskCount);
System.out.println("Sleep per task: " + sleepMs + "ms\n");
// 测试虚拟线程
testVirtualThreads(taskCount, sleepMs);
// 测试平台线程(限制数量,避免 OOM)
testPlatformThreads(taskCount, sleepMs);
}
static void testVirtualThreads(int count, int sleepMs) throws Exception {
System.out.println("--- Virtual Threads ---");
long start = System.currentTimeMillis();
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
for (int i = 0; i < count; i++) {
executor.submit(() -> {
try {
Thread.sleep(sleepMs);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
} // 自动等待所有任务完成
long duration = System.currentTimeMillis() - start;
System.out.println("Completed in: " + duration + "ms");
System.out.println("Throughput: " + (count * 1000L / duration) + " tasks/sec\n");
}
static void testPlatformThreads(int count, int sleepMs) throws Exception {
System.out.println("--- Platform Threads (Fixed Pool) ---");
int poolSize = Runtime.getRuntime().availableProcessors() * 2;
System.out.println("Pool size: " + poolSize);
long start = System.currentTimeMillis();
try (var executor = Executors.newFixedThreadPool(poolSize)) {
for (int i = 0; i < count; i++) {
executor.submit(() -> {
try {
Thread.sleep(sleepMs);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
}
long duration = System.currentTimeMillis() - start;
System.out.println("Completed in: " + duration + "ms");
System.out.println("Throughput: " + (count * 1000L / duration) + " tasks/sec\n");
}
}
典型测试结果:
| 指标 | 平台线程 (200 线程池) | 虚拟线程 |
|---|---|---|
| 10 万任务耗时 | ~50,000ms | ~1,500ms |
| 吞吐量 | ~2,000 tasks/sec | ~66,000 tasks/sec |
| 内存占用 | 高(每个线程 1MB) | 极低(每个约 1KB) |
四、最佳实践与注意事项
4.1 何时使用虚拟线程
✅ 适合使用虚拟线程的场景:
- 高并发的 Web 服务
- I/O 密集型任务(HTTP 调用、数据库操作)
- 需要处理大量并发连接的服务器
❌ 不适合使用虚拟线程的场景:
- CPU 密集型计算(虚拟线程不会提升计算性能)
- 需要精确控制线程亲和性的场景
- 大量使用
ThreadLocal的遗留代码(需谨慎)
4.2 避免虚拟线程的陷阱
// ❌ 错误:在虚拟线程中使用 synchronized 会阻塞载体线程
synchronized (lock) {
// 长时间操作会阻塞虚拟线程调度
Thread.sleep(10000);
}
// ✅ 正确:使用 ReentrantLock 替代 synchronized
var lock = new ReentrantLock();
lock.lock();
try {
// 长时间操作不会阻塞载体线程
Thread.sleep(10000);
} finally {
lock.unlock();
}
4.3 监控和调优
// 获取虚拟线程的调试信息
System.setProperty("jdk.virtualThreadScheduler.parallelism", "4");
System.setProperty("jdk.virtualThreadScheduler.maxPoolSize", "256");
// 监控虚拟线程
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
long[] threadIds = threadMXBean.getAllThreadIds();
ThreadInfo[] threadInfos = threadMXBean.getThreadInfo(threadIds);
for (ThreadInfo info : threadInfos) {
if (info != null && info.getThreadName().startsWith("VirtualThread")) {
System.out.println(info.getThreadName() + " - " + info.getThreadState());
}
}
五、总结
Java 21 的虚拟线程是并发编程的一次革命性升级。它让开发者能够以同步代码的简洁性,获得异步编程的高性能。
核心优势回顾:
- 极简的编程模型:无需 CompletableFuture 或回调地狱
- 极高的并发能力:轻松处理百万级并发连接
- 无缝的框架集成:Spring Boot 3.2+ 原生支持
虚拟线程并非万能,但对于 I/O 密集型的高并发场景,它无疑是最佳选择。随着 Java 21 的普及,虚拟线程必将成为现代 Java 应用的标准配置。
参考资源
Java 21 虚拟线程:彻底告别阻塞,高并发应用的终极解决方案
https://www.zztzz.com.cn/posts/37/ 部分信息可能已经过时









