当前位置: 首页 > news >正文

深入分析---虚拟线程VS传统多线程

目录

一、环境要求

二、为什么引进虚拟线程

三、创建虚拟线程的常用方法

1. 使用Thread.startVirtualThread()

结果

​编辑

2. 使用Thread.Builder

结果

3. 使用ExecutorService

4.使用ThreadFactory 

解释(参见注释)

四、虚拟线程&传统线程(代码比较)

1.创建10000个线程的对比

①传统多线程

解释

结果

 ②虚拟线程

结果

2.Web服务器请求处理对比

我们将关注下面四项指标

服务器端实现

①平台线程池服务器(8080)

②虚拟线程服务器(8085)

测试客户端实现

结果

五、虚拟线程的实现原理

1. 调度模型

2. 载体线程(Carrier Threads)

3. 栈管理

4. 挂起与恢复机制

六、虚拟线程的适用场景

1. 理想使用场景

2. 不适用场景

七、对比

什么叫"自动卸载"?

 比喻


在Java并发编程领域,虚拟线程(Virtual Threads)作为Java 19引入的预览特性并在Java 21中正式发布,代表了并发模型的一次重大革新。本文将深入探讨虚拟线程与传统平台线程(Platform Threads)的区别,通过代码示例展示它们的实现方式,分析性能差异,并详细解析虚拟线程的创建方法和底层原理。

一、环境要求

Idea版本:2024.x+

Jdk版本:21+

二、为什么引进虚拟线程

一句话总结:把“线程”变成和对象一样轻,可以随手开几十万甚至上百万条并发任务,而不用担心内存被栈吃光、CPU 被调度拖垮。

虚拟线程引进本来就是为了解决传统多线程的硬伤

  1. 太重:一个平台线程栈默认 1 MB,10 万条≈100 GB 内存,直接 OOM。

  2. 太少:操作系统线程是稀缺资源,几万条就把调度器压垮。

  3. 太贵:每次创建/销毁、上下文切换都要经过内核,CPU 时间浪费在调度而不是业务。

虚拟线程把“线程”变成 用户态对象+少量栈缓冲,创建/阻塞/切换都在 JVM 里完成,从而把“并发数量级”从 千级 提升到 百万级,同时保持与原有 Thread API 100 % 兼容。

三、创建虚拟线程的常用方法

1. 使用Thread.startVirtualThread()

Thread.startVirtualThread(() -> {System.out.println("Virtual thread running: " + Thread.currentThread());
});

【注】在 Lambda 表达式中,() -> { ... } 就是 Runnable 的简化写法,大括号里的代码就是线程要执行的逻辑。

所以上面的代码其实等价于:

Thread.startVirtualThread(new Runnable() {@Overridepublic 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. 向操作系统申请栈内存(默认 1 MB),合计 ≈ 10 GB;

  2. 在内核调度器里注册 10 000 个调度实体;

  3. 同时睡眠 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 虚拟线程)直接决定内存、上下文切换、延迟。

我们将关注下面四项指标

  1. 吞吐量(Requests/Second)

  2. 延迟(Latency)

  3. 资源利用率(CPU、内存)

  4. 线程数(活跃线程/虚拟线程挂载数)

服务器端实现

平台线程池服务器(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. 挂起与恢复机制

当虚拟线程遇到阻塞操作时:

  1. 当前状态(包括栈帧和局部变量)被保存到堆内存

  2. 载体线程被释放以执行其他虚拟线程

  3. 当I/O操作完成时,虚拟线程被重新调度到可用载体线程

  4. 之前的状态被恢复,执行继续

六、虚拟线程的适用场景

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),出租车只能一直等他,别人打不了这辆出租车。

http://www.lryc.cn/news/587334.html

相关文章:

  • 力扣刷题记录(c++)09
  • 在 OCI 生成式 AI 上搭一个「指定地区拉面店 MCP Server」——从 0 到 1 实战记录
  • opencv中contours的使用
  • 【设计模式】策略模式(政策(Policy)模式)
  • Java小白-设计模式
  • Java 接口 剖析
  • 操作系统-第四章存储器管理和第五章设备管理-知识点整理(知识点学习 / 期末复习 / 面试 / 笔试)
  • 什么是渐进式框架
  • 什么时候会用到 concurrent.futures?要不要背?
  • 17.使用DenseNet网络进行Fashion-Mnist分类
  • 2024CVPR:Question Aware Vision Transformer for Multimodal Reasoning介绍
  • Action-Agnostic Point-Level Supervision for Temporal Action Detection
  • 【读书笔记】《Effective Modern C++》第4章 Smart Pointers
  • 从零开始学习深度学习—水果分类之PyQt5App
  • gcc 源码阅读--C语言预处理
  • 深度学习16(对抗生成网络:GAN+自动编码器)
  • 深入理解 Java JVM
  • Java: OracleHelper
  • MYSQL笔记2
  • 线性基学习笔记
  • 查看Linux服务器显卡使用情况的详细教程
  • 【UE教程/进阶】使用Slate
  • 【unitrix】 5.0 第二套类型级二进制数基本结构体(types2.rs)
  • SQL预编译:安全高效数据库操作的关键
  • 苍穹外卖Day3
  • markdown-it-mathjax3-pro —— 新一代 Markdown 数学公式渲染插件
  • vue的优缺点
  • 框架和库的区别
  • day16~17-系统负载高故障与磁盘管理
  • muduo概述