Java jdk21- 444 Virtual Threads 虚拟线程
三、核心类库改进
444 Virtual Threads 虚拟线程
虚拟线程 Virtual Threads,也称为“用户模式线程(user-mode threads)”或“纤程(fibers)”。
Java21中新引入虚拟线程是一种线程抽象,它提供了一种轻量级的线程实现方式,可以在用户级别进行线程调度和管理。
虚拟线程提供了一种更高效、更轻量级的线程模型,可以在Java应用程序中创建数百万甚至数十亿个线程,而不会受到操作系统线程数量的限制。这使得Java应用程序能够更好地适应大规模并发场景,并提供更高的性能和可伸缩性。
而传统的平台线程是重量级的线程,即它们使用大量计算资源,且与os线程1:1绑定,这意味着线程执行时间依赖CPU执行时间。由于操作系统线程的数量有限,当应用程序需要创建大量线程时,可能会导致资源耗尽或性能下降。
虚拟线程与传统线程核心区别
1)传统Java线程是由操作系统级别的线程,需要在JVM外部进行管理和调度。
2)虚拟线程则是由JVM内部实现的,可以在JVM内部进行管理和调度。
| 维度 | 虚拟线程(Virtual Thread) | 传统线程(Carrier Java Thread) |
|---|---|---|
| 资源消耗 | 轻量级(仅JVM内部调度开销) | 重(依赖操作系统内核线程) |
| 并发能力 | 支持百万级并发 | 受操作系统线程数限制(通常数千) |
| 调度粒度 | JVM内部调度(用户态) | 操作系统调度(内核态) |
| 适用场景 | 高并发、短任务(如Web请求) | 长任务、需系统资源的场景 |
虚拟线程的工作原理
传统的物理线程(也叫平台线程)是基于操作系统线程来实现的,而操作系统线程属于重量级的线程,创建和上下文切换都是非常消耗系统资源的;而虚拟线程是一种更加轻量级的线程,它打破了平台线程和操作系统线程之间一一对应的限制,咱们可以轻松地创建出百万甚至干万级别的线程。
虚拟线程是工作在物理线程(平台线程)之上的,任务最终还是被物理线程执行的,优点是虚拟线程在等待的时候会将它持有的物理线程释放,这样其他的虚拟线程就可以获得这些被释放的物理线程继续执行。

平台线程是被操作系统调度的,而虚拟线程是直接被JDK调度的,因此虚拟线程在切换时可以显著地降低开销。
高并发文件下载器
如果需要同时下载1000个网络文件(IO密集型任务),对比传统平台线程与虚拟线程的实现差异及效果。
一、传统平台线程方案(问题暴露)
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class PlatformThreadDownloader2 {
public static void download(String url) {
// 模拟网络IO:建立连接、读取文件(阻塞操作)
try {
Thread.sleep(1000); // 模拟IO耗时1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
// 实现下载逻辑
System.out.println("下载完成:" + url);
}
public static void main(String[] args) {
// 记录总开始时间
long totalStartTime = System.currentTimeMillis();
// 配置参数
int threadCount = 200;
int taskCount = 1000;
// 传统线程池:假设核心线程数设为200(受限于内存和系统资源)
ExecutorService executor = Executors.newFixedThreadPool(threadCount);
// 使用CountDownLatch来同步所有任务的完成
CountDownLatch latch = new CountDownLatch(taskCount);
// 模拟1000个下载任务
for (int i = 0; i < taskCount; i++) {
int taskId = i;
// 提交任务到线程池
executor.execute(() -> {
// 模拟下载操作
download("http://example.com/file" + taskId+".txt");
// 计数器减1
latch.countDown();
});
}
try {
// 等待所有任务完成,最多等待10分钟
boolean completed = latch.await(10, TimeUnit.MINUTES);
if (!completed) {
System.out.println("警告:部分任务执行超时!");
executor.shutdownNow();
System.out.println("已强制终止超时任务");
return;
}
// 优雅关闭线程池
executor.shutdown();
long totalEndTime = System.currentTimeMillis();
// 计算总耗时
long totalCost = totalEndTime - totalStartTime;
System.out.println("\n=== 执行结果统计 ===");
System.out.println("任务总数: " + taskCount);
System.out.println("线程池大小: " + threadCount);
System.out.println("总耗时: " + totalCost + "ms");
System.out.println("平均每个任务耗时: " + (totalCost / (double)taskCount) + "ms");
System.out.println("吞吐量: " + (taskCount / (totalCost / 1000.0)) + " 任务/秒");
} catch (InterruptedException e) {
System.out.println("程序被中断");
Thread.currentThread().interrupt();
executor.shutdownNow();
}
}
}

问题分析:
1.资源限制:
每个平台线程默认栈内存1MB,200个线程占用约200MB内存。若强行创建1000个平台线程,内存占用达1GB+,可能触发OutOfMemoryError
2. 任务排队:
1000个任务需在200个线程中排队执行,总耗时≈(1000/200) * 1秒 = 5秒,吞吐量低。
3.CPU利用率低:
线程在downloadFile中99%时间处于IO阻塞(Thread.sleep模拟),但仍占用线程资源,导致CPU空闲。
二、虚拟线程方案(优化对比)
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class VirtualThreadDownLoader {
public static void main(String[] args) {
long start = System.nanoTime(); // 纳秒级精度: 1毫秒 = 1,000,000纳秒
// 虚拟线程池:自动调度到少量平台线程(载体线程)
try(ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();){
//直接创建1000个虚拟线程任务
for (int i = 0; i <1000 ; i++) {
int taskId = i;
executor.execute(() -> download("http://example.com/file" + taskId+".txt"));
}
}
long end = System.nanoTime();
System.out.println("耗时:" + (end - start) / 1_000_000 + "ms");
}
public static void download(String url) {
// 模拟网络IO:虚拟线程在阻塞时自动释放载体线程,不影响其他虚拟线程的执行
try {
Thread.sleep(1000); // 阻塞时JVM自动挂起虚拟线程
} catch (InterruptedException e) {
e.printStackTrace();
}
// 实现下载逻辑
System.out.println("下载完成:" + url);
}
}

优化分析:
- 超轻量级资源占用:
- 每个虚拟线程初始栈内存仅几百字节,1000个虚拟线程总内存仅几MB,远低于平台线程。
- M调度复用载体线程:
- JVM自动将1000个虚拟线程调度到少量平台线程(如8核CPU可能仅用8个载体线程)。
- 虚拟线程执行Thread.sleep(IO阻塞)时,JVM自动挂起该线程并释放载体线程,供其他虚拟线程使用。
- 吞吐量提升:
1000个任务可并行阻塞(IO等待期间不占用载体线程),总耗时≈1秒(所有任务同时开始IO,1秒后全部完成),吞吐量提升5倍。
总之:
- 传统方案:200个线程需5秒完成1000个任务,内存占用200MB+。
- 虚拟线程方案:8个载体线程1秒完成1000个任务,内存占用仅几MB,CPU利用率接近100%。
更多推荐

所有评论(0)