跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI 生图工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
Javajava

深入理解 Java 虚拟线程 (Project Loom)

介绍 Java 21 虚拟线程(Project Loom)的背景、原理及优势。对比了传统平台线程与虚拟线程在创建成本、调度机制上的差异,指出虚拟线程适用于 I/O 密集型场景,能实现高并发且保持同步代码风格。文章包含核心概念(挂载/卸载、钉住)、迁移策略、最佳实践及性能压测数据,展示了虚拟线程在吞吐量上的显著提升。

星云发布于 2026/3/23更新于 2026/9/311K 浏览

1. 背景与动机:我们为何需要虚拟线程?

在很多现代编程语言中,比如 Go 的 Goroutine、C# 的 async/await、Erlang 的进程、Lua 的协程,都存在一种'轻量级线程'或'协程'技术。它们的核心目标是用更低的成本来处理并发,尤其是 I/O 阻塞型操作。

曾几何时,我们 Java 开发者面对这些高效的并发模型,只能羡慕地看着。传统的 Java 平台线程(Platform Thread)与操作系统(OS)线程是 1:1 的映射,这使得它成为一种'重型'资源:

  • 创建成本高:每创建一个平台线程,操作系统都需要为其分配一个独立的、通常大小为 1MB 或更多的栈空间。
  • 数量有限:受限于内存和操作系统内核参数,一个 JVM 进程通常只能创建几千个平台线程。
  • 上下文切换昂贵:线程调度由 OS 内核完成,涉及用户态到内核态的切换,开销较大。

这些限制迫使我们在高并发 I/O 场景下,不得不转向复杂的异步编程范式,比如 CompletableFuture 和响应式编程框架(Reactive Frameworks 如 WebFlux)。虽然这些方案能解决问题,但它们带来了新的挑战:'回调地狱'、陡峭的学习曲线、以及与命令式、顺序化代码风格的割裂,使得代码的编写、调试和维护都变得更加困难。

现在,随着 Java 21 中虚拟线程(Virtual Threads)的正式发布(GA),我们终于可以告别这种两难处境。虚拟线程让我们能够用最直观、最简单的同步阻塞式代码,去实现千万级别的海量并发。

2. 为什么引入虚拟线程?

让我们通过一个故事来理解虚拟线程的价值。

阶段一:同步阻塞

同事接到一个任务:根据前端传来的 fileId,从文件服务器下载文件,解析内容,然后处理。他很快写出了同步代码:

public void doSomething(String fileId) {
    String filePath = downloadFile(fileId); // I/O 阻塞
    List<XxxDTO> list = readFile(filePath); // I/O 阻塞
    doSomething(list);
}

这在请求量小的时候工作得很好。

阶段二:天真的并发

随着业务增长,接口变慢。同事学习了多线程,试图为每个请求创建一个新线程来'提速':

public void doSomething(String fileId) {
    new Thread(() -> {
        String filePath = downloadFile(fileId);
        List<XxxDTO> list = readFile(filePath);
        doSomething(list);
    }).start();
}

压测时发现,系统很快就因为创建了过多线程而内存溢出(OutOfMemoryError: unable to create new native thread),并且由于频繁的上下文切换,性能不升反降。

阶段三:线程池优化

在大佬指点下,同事学会了使用线程池来复用和管理线程,避免了资源的无限创建:

public void doSomething(String fileId) {
    executor.submit(() -> {
        String filePath = downloadFile(fileId);
        List<XxxDTO> list = readFile(filePath);
        doSomething(list);
    });
}

这套方案在很长一段时间内都表现良好。但线程池并非银弹,它有其固有的问题:

  • 吞吐量受限:池中线程数量是有限的。当所有线程都在执行 I/O 阻塞操作时(比如等待慢速的网络响应),新的任务只能在队列中等待,导致吞吐量瓶颈和响应延迟增加。
  • 调优复杂:如何设置核心线程数、最大线程数、队列长度,是一个棘手的'猜谜游戏',需要根据负载反复调整。

阶段四:虚拟线程的革命

当请求量达到新的量级,即便是线程池也捉襟见肘。这时,虚拟线程登场了。它完美地解决了上述问题:当一个虚拟线程执行 I/O 阻塞操作时,它不会占用宝贵的平台线程。JVM 会自动将其'挂起'(unmount),并让其底层的'载体线程'(Carrier Thread)去执行其他就绪的虚拟线程。

这意味着,我们可以回归到最简单、最经典的'一个请求一个线程'模型,而无需担心资源耗尽。我们可以创建数百万个虚拟线程,轻松应对海量并发连接。

3. 什么是虚拟线程?

  • 轻量级:由 JVM 在用户空间管理,而非 OS 内核。它的创建和销毁成本极低,内存占用也非常小(几百字节起步)。
  • M:N 调度:M 个虚拟线程被调度在 N 个平台线程上运行(M >> N)。默认情况下,JVM 使用一个与 CPU 核心数相当的 ForkJoinPool 作为载体线程池。
  • 延续性(Continuations)实现:这是虚拟线程的魔法核心。当虚拟线程阻塞时,它的执行堆栈被保存为一个 Continuation 对象。当阻塞操作完成后,JVM 会找到一个可用的载体线程,恢复这个 Continuation 继续执行。
  • 目标:简化高并发 I/O 密集型应用的编程模型,而不是取代平台线程。对于 CPU 密集型任务,平台线程仍然是最佳选择。

工作原理:

  • 虚拟线程(Virtual Threads)也被称为纤程(Fibers),其基本的工作原理是将线程的调度从操作系统级别转移到用户级别,即由 JVM 控制。
  • 在传统的线程模型中,每个线程都对应一个操作系统级别的线程,这种线程的创建、切换和销毁等操作都需要系统调用,消耗较大。而且,每个线程都需要一个完整的线程栈,这限制了同时运行的线程数量。
  • 相比之下,虚拟线程并不直接对应一个操作系统级别的线程,而是由 JVM 管理和调度。多个虚拟线程可能共享一个操作系统级别的线程。这样,当一个虚拟线程阻塞时,JVM 可以立即切换到另一个虚拟线程,而无需等待操作系统的调度。而且,虚拟线程不需要一个完整的线程栈,所以可以创建大量的虚拟线程。
  • JDK 的虚拟线程调度器是一个以 FIFO 模式运行的 ForkJoinPool,调度器可以通过设置启动参数调整。

传统线程于 OS Thread 关系图:

image-20260109183041532

虚拟线程与平台线程的关系图:

image-20260109183102460

JVM 通过平台线程来管理虚拟线程,一个平台线程可以在不同的时间执行不同的虚拟线程,当虚拟线程被阻塞或等待时,平台线程可以切换到执行另一个虚拟线程,一个平台线程同时只会执行一个虚拟线程。

每个请求对应一个线程的应用代码可以在请求的整个持续时间内在虚拟线程中运行,但虚拟线程仅在 CPU 上执行计算时才消耗操作系统线程。这样可以获得异步编程相同的并发性,并且是以透明方式实现的。当虚拟线程中运行的代码调用 java.* API 中的阻塞 I/O 操作时,运行时执行非阻塞操作系统调用,并自动挂起虚拟线程直到它可以恢复的时候。

对于 Java 开发来说,虚拟线程只是创建成本低廉且数量几乎无限的线程。硬件利用率接近最佳,允许高水平的并发性,从而实现高吞吐量,同时代码实现与 Java 及其工具的多线程设计保持风格一致。

4. 与其他语言协程模型的对比

虽然目标相似,但实现和使用体验上各有千秋。

特性Java 虚拟线程Go GoroutineC# async/awaitErlang 进程Lua 协程
调度方式JVM 调度器;工作窃取Go 运行时;M:N 调度编译器生成状态机,通过 await 关键字协作式调度抢占式轻量级进程调度手动调用 yield/resume 进行协作式调度
内存占用动态增长,初始极小几 KB每个异步方法一个状态机对象几 KB 堆/栈共享栈切片
调试模型与平台线程几乎一致,支持标准 Thread APIGoroutine 专属的内省工具通过 Task 对象追踪调用链基于进程邮箱和消息手动追踪
阻塞 I/O直接使用阻塞式 API,JVM 自动处理直接使用阻塞式 API,运行时处理必须使用 await 配合 Async 后缀的 API基于消息传递,天然非阻塞手动处理阻塞
代码风格同步、顺序化同步、顺序化结构化异步(async/await 语法糖)Actor 模型生成器风格

核心优势总结:Java 虚拟线程最大的优势在于对现有生态的无缝兼容和对开发者的透明性。你不需要学习新的 async/await 语法,也不需要改造海量的第三方库。你只需要将任务提交给虚拟线程执行器,就可以用最熟悉的方式编写代码,享受协程带来的高并发能力。

5. 核心内部概念

  • 挂载/卸载 (Mount / Unmount):虚拟线程运行时,会'挂载'到一个载体线程上。当遇到受支持的阻塞操作(如 Socket.read(), Thread.sleep(), BlockingQueue.take())时,它会自动'卸载',释放载体线程。
  • 钉住 (Pinning):这是使用虚拟线程时需要特别注意的陷阱。某些情况下,虚拟线程无法被卸载,会一直'钉'在载体线程上,即使它发生了阻塞。这会严重影响可伸缩性。常见原因包括:
    1. 执行 synchronized 同步块或方法。
    2. 执行 JNI(Java Native Interface)本地方法。
    3. 执行某些特定的 JVM 内部操作。 解决方案:使用 java.util.concurrent.locks.ReentrantLock 替代 synchronized。
  • 结构化并发 (Structured Concurrency - 预览特性):这是一个强大的编程模型,通过 StructuredTaskScope 来管理一组并发任务的生命周期。它能确保:
    • 任务归属:子任务的生命周期被限定在父任务的作用域内。
    • 错误处理:一个子任务失败,可以立即取消其他所有子任务。
    • 结果聚合:简化了等待所有子任务完成并收集结果的逻辑。 这极大地简化了并发代码的健壮性和可维护性,类似于 Go 的 Context 和 errgroup。

6. 何时使用虚拟线程?

黄金法则:虚拟线程适用于 I/O 密集型或'等待密集型'任务,而非 CPU 密集型任务。

理想场景 (I/O Bound):

  • 处理大量并发的 Web 请求、API 调用。
  • 微服务之间的 RPC 通信。
  • 数据库访问(JDBC)。
  • 消息队列的消费和生产。
  • 任何涉及网络和文件系统读写的操作。

避免场景 (CPU Bound):

  • 复杂的数学计算、科学模拟。
  • 视频编码、图像处理。
  • 大规模数据排序、加密解密。
  • 在这些场景下,继续使用传统的平台线程池或 Parallel Streams。

7. 局限性与注意事项

  1. 不要池化虚拟线程:虚拟线程本身就是轻量级的,创建成本极低。池化它们毫无意义,反而会增加复杂性。应该为每个任务创建一个新的虚拟线程。
  2. 警惕 ThreadLocal:由于可能创建数百万个虚拟线程,如果大量使用 ThreadLocal,每个虚拟线程都会持有一份副本,可能导致巨大的内存消耗。 替代方案:优先使用方法传参。对于需要在调用链中传递的数据,使用 Java 21 引入的作用域值 (Scoped Values - 预览特性),它更高效且不可变。
  3. 注意'钉住' (Pinning):使用诊断工具(如 JFR)来检测和分析线程钉住的情况。
  4. 第三方库兼容性:检查你使用的 I/O 相关库(如数据库驱动、HTTP 客户端)是否已经针对虚拟线程进行了优化,避免它们内部的线程池成为瓶颈。
  5. 监控与诊断:传统的线程 Dump 会变得非常庞大。需要学习使用新的工具和方法:
    • jcmd Thread.print -virtual:只打印虚拟线程信息。
    • Java Flight Recorder (JFR):提供了丰富的虚拟线程事件,如启动、结束、钉住等。

8. 迁移策略

将现有应用迁移到虚拟线程通常很简单:

  1. 升级 JDK:确保项目使用 Java 21 或更高版本。
  2. 识别 I/O 密集型代码:找到那些使用 ExecutorService 处理网络请求、数据库查询等任务的地方。
  3. 替换执行器:将 Executors.newFixedThreadPool() 或 newCachedThreadPool() 替换为 Executors.newVirtualThreadPerTaskExecutor()。
  4. 审计同步块:检查 I/O 操作是否被包裹在 synchronized 块中,如果是,则替换为 ReentrantLock。
  5. 保留 CPU 密集型线程池:不要改动用于处理计算密集型任务的线程池。
  6. 测试与监控:进行充分的性能测试,并使用 JFR 等工具监控虚拟线程的行为和载体线程的利用率。

9. 最佳实践

  • 保持同步阻塞风格:拥抱虚拟线程带来的简单性,不要画蛇添足地混合 CompletableFuture。
  • 任务生命周期要短:遵循'一个任务一个虚拟线程'的模式,任务执行完后线程就销毁。
  • 使用 try-with-resources:对于 ExecutorService,使用 try-with-resources 语句可以确保线程池被正确关闭。
  • 善用结构化并发:当一个任务需要派生多个并发子任务时,优先使用 StructuredTaskScope。
  • 批量处理依然有效:虽然虚拟线程可以处理大量并发,但在数据库操作等场景,适当的批量处理仍然是提升性能的好方法。

10. 代码示例

示例 1:简单的虚拟线程创建

import java.util.concurrent.*;

public class VirtualThreadExamples {
    // 方式一:最简单的方式
    public void simpleStart() {
        Thread.startVirtualThread(() -> System.out.println("虚拟线程已启动 - " + Thread.currentThread()));
    }

    // 方式二:使用构建器,可进行更多配置
    public void builderStart() {
        Thread.ofVirtual().name("my-virtual-thread")
            .uncaughtExceptionHandler((t, e) -> System.err.println("线程 " + t.getName() + " 异常:" + e.getMessage()))
            .start(() -> System.out.println("构建的虚拟线程已启动 - " + Thread.currentThread()));
    }

    // 方式三:创建但不立即启动
    public void unstartedThenRun() throws InterruptedException {
        Thread vt = Thread.ofVirtual().unstarted(() -> System.out.println("延迟启动的虚拟线程 - " + Thread.currentThread()));
        vt.start();
        vt.join(); // 等待其完成
    }

    // 方式四:使用专用的 ExecutorService
    public void perTaskExecutor() throws Exception {
        try (ExecutorService exec = Executors.newVirtualThreadPerTaskExecutor()) {
            Future<String> future = exec.submit(() -> {
                Thread.sleep(100);
                return "任务在虚拟线程中完成";
            });
            System.out.println(future.get());
        }
    }
}

示例 2:重构阻塞工作流

这个例子展示了如何将一个包含下载、解析和聚合的复杂 I/O 密集型任务,用虚拟线程和结构化并发进行重构。

import java.io.IOException;
import java.nio.file.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.stream.Collectors;
import java.util.concurrent.StructuredTaskScope; // 使用 Java 21 标准库

public class VirtualThreadFileProcessor {
    // 用于 I/O 密集型任务的虚拟线程执行器
    private final ExecutorService vExecutor = Executors.newVirtualThreadPerTaskExecutor();
    // 用于 CPU 密集型任务的平台线程池
    private final ExecutorService cpuExecutor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

    public CompletableFuture<ProcessingResult> processFile(String fileId) {
        // 整个流程提交给虚拟线程执行
        return CompletableFuture.supplyAsync(() -> {
            String path = downloadFile(fileId); // 阻塞 I/O
            List<String> lines = readLines(path); // 阻塞文件 I/O
            return parseAndAggregate(lines);
        }, vExecutor);
    }

    private String downloadFile(String fileId) {
        System.out.println("下载文件 " + fileId + " on " + Thread.currentThread());
        try {
            Thread.sleep(Duration.ofMillis(50)); // 模拟网络 I/O
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        return "/tmp/" + fileId + ".dat";
    }

    private List<String> readLines(String path) {
        try {
            // 假设文件已创建
            // Files.createFile(Path.of(path));
            return Files.readAllLines(Path.of(path));
        } catch (IOException e) {
            return List.of();
        }
    }

    private ProcessingResult parseAndAggregate(List<String> lines) {
        // 使用结构化并发来并行解析文件的各个部分
        List<ParsedChunk> parsedChunks;
        try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
            List<Future<ParsedChunk>> futures = new ArrayList<>();
            for (List<String> chunk : splitIntoChunks(lines, 4)) {
                futures.add(scope.fork(() -> parseChunk(chunk)));
            }
            scope.join(); // 等待所有子任务完成
            scope.throwIfFailed(); // 如果有任何子任务失败,则抛出异常
            parsedChunks = futures.stream().map(Future::resultNow).collect(Collectors.toList());
        } catch (InterruptedException | ExecutionException e) {
            Thread.currentThread().interrupt();
            return new ProcessingResult(0, Map.of());
        }
        // 最后的聚合是 CPU 密集型任务,提交给平台线程池
        try {
            return cpuExecutor.submit(() -> summarize(parsedChunks)).get();
        } catch (InterruptedException | ExecutionException e) {
            Thread.currentThread().interrupt();
            return new ProcessingResult(0, Map.of());
        }
    }
}

11. 性能压测

11.1 前置准备

  • Spring Boot: 3.5.5
  • JDK: zulu-21 21.0.9
  • 压测工具:k6
  • 运行配置:win11 12C 36G

11.2 压测代码

模拟 io 请求

压测脚本 simple-test.js

import http from'k6/http';
import{ check }from'k6';
export default function(){
const res = http.get(`${__ENV.url}`);
check(res,{'is status 200':(r)=> r.status ===200});
}

开启虚拟线程 请求同上,增加如下配置

@EnableAsync
@Configuration
@ConditionalOnProperty(value ="spring.executor", havingValue ="virtual")
public class ThreadConfig{
@Bean
public AsyncTaskExecutor applicationTaskExecutor(){
return new TaskExecutorAdapter(Executors.newVirtualThreadPerTaskExecutor());
}
@Bean
public TomcatProtocolHandlerCustomizer<?> protocolHandlerVirtualThreadExecutorCustomizer(){
return protocolHandler ->{
protocolHandler.setExecutor(Executors.newVirtualThreadPerTaskExecutor());
};
}
}

application.yml

spring:
#配置 virtual 表示启用虚拟线程,非 virtual 表示不启用
executor: virtual
threads:
virtual:
enabled:true

未开启虚拟线程

@GetMapping("/test/{timeMillis}")
public Object test(@PathVariable long timeMillis) throws InterruptedException{
Map<String,Object> map =newHashMap<>();
map.put("time",System.currentTimeMillis());
map.put("msg","test");
map.put("thread",Thread.currentThread().toString());
Thread.sleep(timeMillis);
return map;
}

11.3 压测结果

依次调整-u 参数 (200,400,600,800,1000) 并执行如下命令

k6 run -u 200 --duration 60s -e url=http://127.0.0.1:8080/test/100 simple-test.js

压测结果

未开启虚拟线程

  1. 200 并发,逐步递增结果 HTTP http_req_duration..............: avg=108.84ms min=100.09ms med=108.58ms max=186.45ms p(90)=110.68ms p(95)=111.62ms { expected_response:true }...: avg=108.84ms min=100.09ms med=108.58ms max=186.45ms p(90)=110.68ms p(95)=111.62ms http_req_failed................: 0.00% 0 out of 110184 http_reqs......................: 110184 1833.622197/s EXECUTION iteration_duration.............: avg=108.99ms min=100.36ms med=108.73ms max=186.45ms p(90)=110.82ms p(95)=111.77ms iterations.....................: 110184 1833.622197/s vus............................: 200 min=200 max=200 vus_max........................: 200 min=200 max=200 NETWORK data_received..................: 25 MB 409 kB/s data_sent......................: 8.7 MB 145 kB/s
  2. 400 并发,逐步递增结果 HTTP http_req_duration..............: avg=215.23ms min=100.44ms med=216.83ms max=263.05ms p(90)=219.37ms p(95)=220.42ms { expected_response:true }...: avg=215.23ms min=100.44ms med=216.83ms max=263.05ms p(90)=219.37ms p(95)=220.42ms http_req_failed................: 0.00% 0 out of 111634 http_reqs......................: 111634 1854.160819/s EXECUTION iteration_duration.............: avg=215.34ms min=100.44ms med=216.93ms max=263.05ms p(90)=219.49ms p(95)=220.53ms iterations.....................: 111634 1854.160819/s vus............................: 400 min=400 max=400 vus_max........................: 400 min=400 max=400 NETWORK data_received..................: 25 MB 414 kB/s data_sent......................: 8.8 MB 147 kB/s
  3. 600 并发,逐步递增结果 HTTP http_req_duration..............: avg=322.04ms min=0s med=325.31ms max=389.61ms p(90)=328.76ms p(95)=330.02ms { expected_response:true }...: avg=322.79ms min=101.37ms med=325.32ms max=389.61ms p(90)=328.77ms p(95)=330.02ms http_req_failed................: 0.23% 259 out of 111969 http_reqs......................: 111969 1856.220501/s EXECUTION iteration_duration.............: avg=322.24ms min=2.16ms med=325.43ms max=390.61ms p(90)=328.84ms p(95)=330.09ms iterations.....................: 111969 1856.220501/s vus............................: 600 min=600 max=600 vus_max........................: 600 min=600 max=600 NETWORK data_received..................: 25 MB 413 kB/s data_sent......................: 8.8 MB 146 kB/s
  4. 800 并发,逐步递增结果 HTTP http_req_duration..............: avg=430.68ms min=100.61ms med=433.68ms max=540.35ms p(90)=437.1ms p(95)=438.25ms { expected_response:true }...: avg=430.68ms min=100.61ms med=433.68ms max=540.35ms p(90)=437.1ms p(95)=438.25ms http_req_failed................: 0.38% 421 out of 111765 http_reqs......................: 111765 1849.543354/s EXECUTION iteration_duration.............: avg=430.8ms min=100.6ms med=433.79ms max=540.34ms p(90)=437.2ms p(95)=438.33ms iterations.....................: 111765 1849.543354/s vus............................: 800 min=800 max=800 vus_max........................: 800 min=800 max=800 NETWORK data_received..................: 25 MB 413 kB/s data_sent......................: 8.8 MB 146 kB/s
  5. 1000 并发,逐步递增结果 HTTP http_req_duration..............: avg=529.78ms min=0s med=542.67ms max=609.58ms p(90)=546.22ms p(95)=547.36ms { expected_response:true }...: avg=532.93ms min=100.4ms med=542.71ms max=609.58ms p(90)=546.22ms p(95)=547.37ms http_req_failed................: 0.58% 664 out of 112577 http_reqs......................: 112577 1859.599297/s EXECUTION iteration_duration.............: avg=535.02ms min=26.48ms med=542.82ms max=1.82s p(90)=546.35ms p(95)=547.61ms iterations.....................: 112577 1859.599297/s vus............................: 1000 min=1000 max=1000 vus_max........................: 1000 min=1000 max=1000 NETWORK data_received..................: 25 MB 413 kB/s data_sent......................: 8.8 MB 146 kB/s 开启虚拟线程
  6. 200 并发,逐步递增结果 HTTP http_req_duration..............: avg=108.66ms min=100.25ms med=108.51ms max=176.59ms p(90)=110.48ms p(95)=111.32ms { expected_response:true }...: avg=108.66ms min=100.25ms med=108.51ms max=176.59ms p(90)=110.48ms p(95)=111.32ms http_req_failed................: 0.00% 0 out of 110299 http_reqs......................: 110299 1835.872398/s EXECUTION iteration_duration.............: avg=108.8ms min=100.25ms med=108.64ms max=177.59ms p(90)=110.64ms p(95)=111.48ms iterations.....................: 110299 1835.872398/s vus............................: 200 min=200 max=200 vus_max........................: 200 min=200 max=200 NETWORK data_received..................: 28 MB 472 kB/s data_sent......................: 8.7 MB 145 kB/s
  7. 400 并发,逐步递增结果 HTTP http_req_duration..............: avg=107.99ms min=0s med=108.28ms max=164.37ms p(90)=110.43ms p(95)=111.35ms { expected_response:true }...: avg=108.07ms min=99.72ms med=108.28ms max=164.37ms p(90)=110.43ms p(95)=111.35ms http_req_failed................: 0.07% 170 out of 221398 http_reqs......................: 221398 3684.402592/s EXECUTION iteration_duration.............: avg=108.43ms min=52.76ms med=108.42ms max=730.24ms p(90)=110.61ms p(95)=111.58ms iterations.....................: 221398 3684.402592/s vus............................: 400 min=400 max=400 vus_max........................: 400 min=400 max=400 NETWORK data_received..................: 57 MB 952 kB/s data_sent......................: 18 MB 291 kB/s
  8. 600 并发,逐步递增结果 HTTP http_req_duration..............: avg=110.42ms min=0s med=108.79ms max=423.9ms p(90)=118.33ms p(95)=121.75ms { expected_response:true }...: avg=110.66ms min=99.83ms med=108.79ms max=423.9ms p(90)=118.34ms p(95)=121.76ms http_req_failed................: 0.21% 676 out of 321730 http_reqs......................: 321730 5351.582218/s EXECUTION iteration_duration.............: avg=111.88ms min=1.76ms med=108.95ms max=1.6s p(90)=118.67ms p(95)=122.18ms iterations.....................: 321730 5351.582218/s vus............................: 600 min=600 max=600 vus_max........................: 600 min=600 max=600 NETWORK data_received..................: 83 MB 1.4 MB/s data_sent......................: 25 MB 422 kB/s
  9. 800 并发,逐步递增结果 HTTP http_req_duration..............: avg=128.88ms min=0s med=120.97ms max=774.13ms p(90)=152.35ms p(95)=184.17ms { expected_response:true }...: avg=129.15ms min=100.51ms med=120.99ms max=774.13ms p(90)=152.4ms p(95)=184.34ms http_req_failed................: 0.20% 752 out of 361920 http_reqs......................: 361920 6021.494579/s EXECUTION iteration_duration.............: avg=132.49ms min=32.43ms med=121.36ms max=2.95s p(90)=156.14ms p(95)=192.7ms iterations.....................: 361920 6021.494579/s vus............................: 800 min=800 max=800 vus_max........................: 800 min=800 max=800 NETWORK data_received..................: 94 MB 1.6 MB/s data_sent......................: 29 MB 475 kB/s
  10. 1000 并发,逐步递增结果 HTTP http_req_duration..............: avg=128.56ms min=0s med=121.77ms max=652.69ms p(90)=149.55ms p(95)=175.48ms { expected_response:true }...: avg=128.8ms min=100.27ms med=121.79ms max=652.69ms p(90)=149.58ms p(95)=175.55ms http_req_failed................: 0.18% 837 out of 450901 http_reqs......................: 450901 7489.132676/s EXECUTION iteration_duration.............: avg=132.99ms min=10.83ms med=122.25ms max=4.98s p(90)=153.34ms p(95)=183.58ms iterations.....................: 450901 7489.132676/s vus............................: 1000 min=1000 max=1000 vus_max........................: 1000 min=1000 max=1000 NETWORK data_received..................: 117 MB 1.9 MB/s data_sent......................: 36 MB 591 kB/s

11.4 压测结果分析

  1. 未开启虚拟线程 vs 开启虚拟线程对比表
Case类型QPS平均延迟p95 延迟失败率
-u 200传统线程1833.62108.84ms111.62ms0.00%
-u 200虚拟线程1835.87108.66ms111.32ms0.00%
-u 400传统线程1854.16215.23ms220.42ms0.00%
-u 400虚拟线程3684.40107.99ms111.35ms0.07%
-u 600传统线程1856.22322.04ms330.02ms0.23%
-u 600虚拟线程5351.58110.42ms121.75ms0.21%
-u 800传统线程1849.54430.68ms438.25ms0.38%
-u 800虚拟线程6021.49128.88ms184.17ms0.20%
-u 1000传统线程1859.60529.78ms547.36ms0.58%
-u 1000虚拟线程7489.13128.56ms175.48ms0.18%
  1. QPS 对比

未开启虚拟线程:≈1850 req/s (各并发级别基本持平) 开启虚拟线程:从≈1835 增长至≈7489 req/s (+306%)

  1. 平均延迟对比

未开启虚拟线程:从 108.84ms 增至 529.78ms (+387%) 开启虚拟线程:维持在 107.99ms-128.88ms 之间 (相对稳定)

  1. P95 延迟对比

未开启虚拟线程:从 111.62ms 增至 547.36ms (+390%) 开启虚拟线程:从 111.32ms 增至 175.48ms (+57.6%)

结论

  1. 吞吐量显著提升 在 1000 并发下,开启虚拟线程的 QPS 是未开启时的 4 倍以上 随着并发数增加,传统线程模式性能趋于饱和,而虚拟线程模式仍能线性增长
  2. 延迟表现优异 未开启虚拟线程时,平均延迟随并发数线性增长 (108ms→529ms) 开启虚拟线程后,平均延迟保持稳定 (107ms-128ms) P95 延迟增长幅度明显减缓,虚拟线程优势明显
  3. 系统稳定性更好 传统线程模式失败率随并发数递增 (0%→0.58%) 虚拟线程模式失败率维持在较低水平 (0.07%→0.18%)
  4. 资源利用效率更高 相同硬件条件下,虚拟线程能处理 4 倍以上的并发请求 网络传输速度也有相应提升,说明整体系统效率更高

12. 总结

虚拟线程是 Java 并发编程的一次重大飞跃。它让我们能够:

  • 以简单的同步代码风格,编写出具备极高吞吐量的 I/O 密集型应用。
  • 显著降低资源消耗,轻松支持百万级并发连接。
  • 摆脱复杂的异步和响应式编程范式,提高开发效率和代码可维护性。

对于我们团队而言,这意味着在未来的项目中,面对高并发 I/O 场景,我们可以优先考虑使用虚拟线程来构建更简单、更健壮、更易于维护的系统。

目录

  1. 1. 背景与动机:我们为何需要虚拟线程?
  2. 2. 为什么引入虚拟线程?
  3. 3. 什么是虚拟线程?
  4. 4. 与其他语言协程模型的对比
  5. 5. 核心内部概念
  6. 6. 何时使用虚拟线程?
  7. 7. 局限性与注意事项
  8. 8. 迁移策略
  9. 9. 最佳实践
  10. 10. 代码示例
  11. 11. 性能压测
  12. 11.1 前置准备
  13. 11.2 压测代码
  14. 11.3 压测结果
  15. 11.4 压测结果分析
  16. 12. 总结

更多推荐文章

查看全部
  • QGroundControl 跨平台安装指南:Windows、macOS、Linux 与 Android 部署实战
  • STL map/multimap 接口使用与核心特性详解
  • Llama-2-7B 在昇腾 NPU 上的性能测评与部署方案
  • FPGA入门:CAN总线原理与Verilog代码详解
  • 深度解析 KBQA 常用数据集:WebQSP 与 CWQ
  • Python Gymnasium 库详解:强化学习环境完整使用指南与示例
  • 上下文协议(MCP)Java SDK 指南
  • 上下文查询增强:让 AI 智能体理解对话记忆与情境
  • AI 编程工具实测:效率提升背后的真实体验
  • SDXL-Turbo 快速生成高质量 AI 绘画的三项核心技巧
  • LeetCode 380 O(1) 时间插入、删除和获取随机元素
  • HDFS 分布式文件系统数据读写流程详解
  • Agent Skills 详解:如何赋予 AI 执行特定任务的能力
  • 三星研究院 Shallow-π:基于知识蒸馏的机器人 VLA 模型优化
  • SDXL-Turbo 使用技巧:提升 AI 绘画质量的 3 个方法
  • SDMatte 服务 SLA 保障方案:99.5% 可用性承诺下的监控告警与应急响应
  • 递归经典实战:汉诺塔、链表操作与快速幂详解
  • Dify 工作流发布为 MCP Server 实战指南
  • 大模型应用:RAG 原理、流程与最佳实践
  • GitHub 2FA 双重验证实战指南:Edge 插件配置方案

相关免费在线工具

  • Keycode 信息

    查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

  • Escape 与 Native 编解码

    JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

  • JavaScript / HTML 格式化

    使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

  • JavaScript 压缩与混淆

    Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online