目录
1. 使用Thread.startVirtualThread()
在Java并发编程领域,虚拟线程(Virtual Threads)作为Java 19引入的预览特性并在Java 21中正式发布,代表了并发模型的一次重大革新。本文将深入探讨虚拟线程与传统平台线程(Platform Threads)的区别,通过代码示例展示它们的实现方式,分析性能差异,并详细解析虚拟线程的创建方法和底层原理。
一、环境要求
Idea版本:2024.x+
Jdk版本:21+
二、为什么引进虚拟线程
一句话总结:把“线程”变成和对象一样轻,可以随手开几十万甚至上百万条并发任务,而不用担心内存被栈吃光、CPU 被调度拖垮。
虚拟线程引进本来就是为了解决传统多线程的硬伤:
太重:一个平台线程栈默认 1 MB,10 万条≈100 GB 内存,直接 OOM。
太少:操作系统线程是稀缺资源,几万条就把调度器压垮。
太贵:每次创建/销毁、上下文切换都要经过内核,CPU 时间浪费在调度而不是业务。
虚拟线程把“线程”变成 用户态对象+少量栈缓冲,创建/阻塞/切换都在 JVM 里完成,从而把“并发数量级”从 千级 提升到 百万级,同时保持与原有 Thread API 100 % 兼容。
三、创建虚拟线程的常用方法
1. 使用Thread.startVirtualThread()
Thread.startVirtualThread(() -> {
System.out.println("Virtual thread running: " + Thread.currentThread());
});
【注】在 Lambda 表达式中,
() -> { ... }就是Runnable的简化写法,大括号里的代码就是线程要执行的逻辑。所以上面的代码其实等价于:
Thread.startVirtualThread(new Runnable() { @Override public void run() { System.out.println("Virtual thread running: " + Thread.currentThread()); } });
结果
同理,还等价于:
// 定义一个Runnable任务
Runnable task = () -> {
// 在这里放置你的任务代码
System.out.println("Virtual thread1 running...");
};
// 直接使用Thread类的静态方法startVirtualThread来创建并启动一个虚拟线程
Thread vt =Thread.startVirtualThread(task);
vt.join();
上面三种表述都是一样的效果。
2. 使用Thread.Builder
Thread.Builder builder = Thread.ofVirtual().name("virtual-thread-", 1);
builder.start(() -> {
System.out.println("Named virtual thread running");
});
结果

3. 使用ExecutorService
// 使用线程池创建虚拟线程
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
executor.submit(() -> {
System.out.println("通过线程池创建的虚拟线程");
});
} // 自动等待所有任务完成
【注】使用
ExecutorService(通过Executors.newVirtualThreadPerTaskExecutor()创建)配合try-with-resources语法,确实会自动等待所有提交的任务完成,无需手动调用join()方法。try (资源声明1; 可选资源2; …) { // 使用资源的代码块 }
4.使用ThreadFactory
ThreadFactory factory = Thread.ofVirtual().factory();
Thread virtualThread = factory.newThread(() -> {
System.out.println("Virtual thread created via factory");
});
virtualThread.start();
解释(参见注释)
package com.qcby.vtTest; import java.util.concurrent.ThreadFactory; public class CreatVT { public static void main(String[] args) throws InterruptedException { /* * 让 JVM 返回一个“专门生产虚拟线程”的工厂对象。 以后每调用一次 factory.newThread(task),就得到一条 尚未启动 的虚拟线程实例。 * */ ThreadFactory factory = Thread.ofVirtual().factory(); /* * 把 Runnable 任务交给工厂,工厂把它包装成一条 新的虚拟线程对象 virtualThread。 此时线程处于 NEW 状态,还没开始跑。 * */ Thread vt = factory.newThread(() -> { System.out.println("Virtual thread created via factory"); }); /* 真正触发 JVM 把这条虚拟线程调度到 极少量 carrier 线程上运行。 线程进入 RUNNABLE 状态,随后执行 System.out.println(...) * */ vt.start(); vt.join(); } }
【注】工厂模式只是把“创建线程”和“启动线程”解耦;底层仍是一条虚拟线程。
四、虚拟线程&传统线程(代码比较)
1.创建10000个线程的对比
①传统多线程
// 不推荐实际运行,可能导致系统资源耗尽
List<Thread> platformThreads = new ArrayList<>();
for (int i = 0; i < 10_000; i++) {
Thread thread = new Thread(() -> { //循环10000次创建线程
try {
Thread.sleep(Duration.ofSeconds(1));
} catch (InterruptedException e) {
e.printStackTrace();
}
});
thread.start();
platformThreads.add(thread);
}
// 等待所有线程完成
for (Thread thread : platformThreads) {
thread.join();
}
解释
向操作系统申请栈内存(默认 1 MB),合计 ≈ 10 GB;
在内核调度器里注册 10 000 个调度实体;
同时睡眠 1 秒,期间仍占着栈和调度资源。
结果
内存瞬间暴涨,很可能触发 OutOfMemoryError。
操作系统线程调度开销激增,系统假死或崩溃。
【注】不要去运行,会导致“卡死”现象。
②虚拟线程
package com.qcby.vtTest;
import java.time.Duration;
import java.util.*;
public class CreatVT {
public static void main(String[] args) throws InterruptedException {
List<Thread> virtualThreads = new ArrayList<>();
int num=0;
for (int i = 0; i < 10_000; i++) {
Thread thread = Thread.startVirtualThread(() -> {
try {
Thread.sleep(Duration.ofSeconds(1));
System.out.println(Thread.currentThread().threadId()+"创建成功");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
virtualThreads.add(thread);
}
// 等待所有线程完成
for (Thread thread : virtualThreads) {
thread.join();
}
}
}
结果

2.Web服务器请求处理对比
Web 服务器请求处理对比,就是把“用传统线程池(Tomcat/Jetty 默认)” 与 “用虚拟线程” 两种方案,放在同一个 Web 服务器场景下,从 并发量、延迟、资源占用 等维度做 A/B 比较。
Web 服务器的场景里,“请求处理” ≈ 并发能力
• 并发量直接决定能同时服务多少用户;
• 并发模型(线程池 vs 虚拟线程)直接决定内存、上下文切换、延迟。
我们将关注下面四项指标
吞吐量(Requests/Second)
延迟(Latency)
资源利用率(CPU、内存)
线程数(活跃线程/虚拟线程挂载数)
服务器端实现
①平台线程池服务器(8080)
import java.io.*;
import java.net.*;
import java.util.concurrent.*;
public class ThreadPoolServer {
private static final int THREAD_POOL_SIZE = 200;
private static final ExecutorService executor =
Executors.newFixedThreadPool(THREAD_POOL_SIZE);
public static void main(String[] args) throws IOException {
int port = 8080;
try (ServerSocket serverSocket = new ServerSocket(port)) {
System.out.println("ThreadPool Server started on port " + port);
while (true) {
Socket clientSocket = serverSocket.accept();
handleRequest(clientSocket);
}
}
}
private static void handleRequest(Socket socket) {
executor.execute(() -> {
try {
// 模拟I/O操作
InputStream input = socket.getInputStream();
byte[] buffer = new byte[1024];
int read = input.read(buffer);
// 处理请求
Thread.sleep(100); // 模拟业务处理时间
// 发送响应
OutputStream output = socket.getOutputStream();
output.write("HTTP/1.1 200 OK\r\n\r\nHello from ThreadPool".getBytes());
socket.close();
} catch (Exception e) {
e.printStackTrace();
}
});
}
}
②虚拟线程服务器(8085)
import java.io.*;
import java.net.*;
public class VirtualThreadServer {
public static void main(String[] args) throws IOException {
int port = 8085;
try (ServerSocket serverSocket = new ServerSocket(port)) {
System.out.println("VirtualThread Server started on port " + port);
while (true) {
Socket clientSocket = serverSocket.accept();
handleRequest(clientSocket);
}
}
}
private static void handleRequest(Socket socket) {
Thread.startVirtualThread(() -> {
try {
// 模拟I/O操作
InputStream input = socket.getInputStream();
byte[] buffer = new byte[1024];
int read = input.read(buffer);
// 处理请求
Thread.sleep(100); // 模拟业务处理时间
// 发送响应
OutputStream output = socket.getOutputStream();
output.write("HTTP/1.1 200 OK\r\n\r\nHello from VirtualThread".getBytes());
socket.close();
} catch (Exception e) {
e.printStackTrace();
}
});
}
}
测试客户端实现
import java.io.*;
import java.net.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.*;
public class LoadTestClient {
private static final int TOTAL_REQUESTS = 10_000;
private static final int CONCURRENT_CLIENTS = 1_000;
public static void main(String[] args) throws InterruptedException {
// 测试线程池服务器
testServer("localhost", 8080, "ThreadPool Server");
// 测试虚拟线程服务器
testServer("localhost", 8085, "VirtualThread Server");
}
private static void testServer(String host, int port, String serverName)
throws InterruptedException {
final AtomicInteger completedRequests = new AtomicInteger();
final AtomicInteger failedRequests = new AtomicInteger();
final CountDownLatch latch = new CountDownLatch(CONCURRENT_CLIENTS);
long startTime = System.currentTimeMillis();
ExecutorService clientExecutor = Executors.newFixedThreadPool(CONCURRENT_CLIENTS);
for (int i = 0; i < CONCURRENT_CLIENTS; i++) {
clientExecutor.execute(() -> {
for (int j = 0; j < TOTAL_REQUESTS / CONCURRENT_CLIENTS; j++) {
try (Socket socket = new Socket(host, port);
OutputStream out = socket.getOutputStream();
InputStream in = socket.getInputStream()) {
// 发送简单HTTP请求
String request = "GET / HTTP/1.1\r\nHost: localhost\r\n\r\n";
out.write(request.getBytes());
out.flush();
// 读取响应
byte[] response = new byte[1024];
int bytesRead = in.read(response);
completedRequests.incrementAndGet();
} catch (Exception e) {
failedRequests.incrementAndGet();
}
}
latch.countDown();
});
}
latch.await();
clientExecutor.shutdown();
long endTime = System.currentTimeMillis();
long totalTime = endTime - startTime;
System.out.println("\n" + serverName + " Test Results:");
System.out.println("Total requests: " + TOTAL_REQUESTS);
System.out.println("Completed requests: " + completedRequests.get());
System.out.println("Failed requests: " + failedRequests.get());
System.out.println("Total time: " + totalTime + " ms");
System.out.println("Throughput: " +
(completedRequests.get() / (totalTime / 1000.0)) + " requests/sec");
}
}
结果


五、虚拟线程的实现原理
1. 调度模型
虚拟线程采用M:N调度模型:
-
M个虚拟线程映射到N个平台线程(N通常等于CPU核心数)
-
JVM负责虚拟线程的调度,而非操作系统
-
当虚拟线程执行阻塞操作时,JVM会自动将其挂起,释放平台线程
【注】平台线程 = OS 线程 = 传统意义上的 Java 线程(
Thread或线程池里的工作线程)
平台 = 操作系统(Windows、Linux、macOS)。
平台线程 = 由操作系统直接创建、调度的线程。
2. 载体线程(Carrier Threads)
虚拟线程运行在平台线程(称为载体线程)上:
-
挂起的虚拟线程不占用载体线程
-
一个载体线程可以依次执行多个虚拟线程
-
JVM维护一个ForkJoinPool作为虚拟线程的默认调度器
3. 栈管理
虚拟线程使用"栈切片"技术:
-
栈空间按需分配和释放
-
挂起时保存栈状态,恢复时重建
-
避免了固定大栈的内存浪费
4. 挂起与恢复机制
当虚拟线程遇到阻塞操作时:
-
当前状态(包括栈帧和局部变量)被保存到堆内存
-
载体线程被释放以执行其他虚拟线程
-
当I/O操作完成时,虚拟线程被重新调度到可用载体线程
-
之前的状态被恢复,执行继续
六、虚拟线程的适用场景
1. 理想使用场景
-
高并发I/O密集型应用(如Web服务器)
-
需要处理大量并发连接
-
包含许多阻塞操作的任务
-
需要简单同步代码但又要高性能的场景
2. 不适用场景
-
CPU密集型任务(无性能优势)
-
需要线程本地存储的重度使用
-
依赖线程优先级或精细线程控制的场景
七、对比
平台线程 = 传统 Java 线程 = 操作系统线程;
虚拟线程 = JVM 在“用户空间”里模拟的“轻量级线程”
| 特性 | 平台线程(传统线程) | 虚拟线程(Virtual Thread) |
|---|---|---|
| 由谁创建 | 操作系统 | JVM |
| 数量上限 | 几千个 | 百万级 |
| 内存占用 | 约 1 MB/线程 | 几百字节起 |
| 切换成本 | 内核态切换,较重 | 纯用户态切换,极轻 |
| 是否会被阻塞 | 会 | 可以自动卸载(前提:不被 synchronized 或 JNI 钉住) |
什么叫"自动卸载"?
虚拟线程在执行 阻塞 I/O(如
socket.read())时,理论上会把当前任务从平台线程上“摘下来”,让这个平台线程去干别的活。这个过程叫 unmount(卸载)。
等 I/O 就绪后,再随便找一个空闲的平台线程把虚拟线程“挂回去”继续跑。
比喻
正常情况:虚拟线程像“乘客”,平台线程像“出租车”。
乘客打车去商场,路上堵车(阻塞 I/O)→ 乘客下车,出租车去接别的单。被钉住:乘客上车后把自己焊死在座位上(进入
synchronized),出租车只能一直等他,别人打不了这辆出租车。

1304

被折叠的 条评论
为什么被折叠?



