Java 21 虚拟线程最佳实践

 更新时间:2026年06月17日 08:12:17   作者:yb779  
本文系统梳理 Java 21 虚拟线程的核心原理、适用场景与落地最佳实践,结合创建示例、结构化并发建议及性能对比,帮助你以更低成本提升高并发服务的吞吐与可维护性

Java 21 的虚拟线程(Virtual Threads)让“一个请求一个线程”重新变得可行。相比传统平台线程,虚拟线程更轻量、创建成本更低、阻塞代价更小,非常适合 I/O 密集型、高并发、请求生命周期短的服务。

但虚拟线程并不是“开了就快”,想真正发挥价值,必须理解它的适用边界、调度特性和最佳实践。本文结合代码示例和性能对比,带你从“能用”走到“用对”。

1. 虚拟线程是什么

虚拟线程由 JVM 管理,底层由少量载体线程(Carrier Threads)执行。它的核心优势是:

  • 创建和销毁成本极低
  • 可承载海量并发任务
  • 在阻塞 I/O 场景下不会像平台线程那样迅速耗尽资源
  • 更适合让代码保持同步写法,降低回调地狱和复杂异步链路

你可以把它理解为:线程仍然是线程,但“重量”被 JVM 接管了。

2. 什么时候适合用虚拟线程

虚拟线程特别适合以下场景:

  • Web 服务请求处理
  • 调用数据库、Redis、HTTP 接口等 I/O 密集型任务
  • 大量短生命周期任务并发执行
  • 希望保留同步编程模型,但又需要高并发能力

不太适合:

  • CPU 密集型计算任务
  • 依赖线程本地状态且设计混乱的老代码
  • 长时间占用锁、频繁做阻塞式同步等待的场景

3. 虚拟线程创建示例

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

public class VirtualThreadDemo {
    public static void main(String[] args) throws InterruptedException {
        Thread vt = Thread.ofVirtual().start(() -> {
            System.out.println("Hello from virtual thread: " + Thread.currentThread());
        });

        vt.join();
    }
}

这段代码与普通线程写法几乎一致,只是把 Thread.ofPlatform() 换成了 Thread.ofVirtual()

示例 2:使用虚拟线程执行多个任务

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class VirtualThreadExecutorDemo {
    public static void main(String[] args) throws InterruptedException {
        try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
            for (int i = 0; i < 10; i++) {
                int taskId = i;
                executor.submit(() -> {
                    System.out.println("Task " + taskId + " running on " + Thread.currentThread());
                    TimeUnit.MILLISECONDS.sleep(200);
                    return null;
                });
            }
        }
    }
}

newVirtualThreadPerTaskExecutor() 是最推荐的入口之一。它保留了熟悉的 ExecutorService 模型,同时让每个任务都运行在虚拟线程上。

4. 最佳实践:优先把“阻塞型业务”迁移到虚拟线程

如果你的服务中存在大量如下代码:

  • JDBC 查询
  • HTTP 调用
  • 文件读写
  • RPC 请求

那么虚拟线程能显著简化并发模型。你可以继续使用同步 API,而不必强行改造成复杂的响应式链路。

推荐做法

  • 保留同步代码风格,避免过度抽象
  • 任务粒度尽量清晰,避免一个虚拟线程里做过多杂事
  • 对外部依赖设置合理超时,避免虚拟线程堆积
  • 结合连接池、限流和熔断控制下游压力

5. 最佳实践:谨慎使用线程池的旧思维

虚拟线程时代,很多旧习惯需要更新。

不建议

  • 继续用大而重的平台线程池去“模拟高并发”
  • 为每类任务手工维护复杂线程池参数
  • 盲目追求线程数越多越好

更合理的方式

  • 对短任务使用虚拟线程 per task 模型
  • 用信号量、限流器控制并发上限
  • 用结构化并发管理一组相关任务

6. 性能对比示例:平台线程 vs 虚拟线程

下面用一个简单的 I/O 模拟任务来对比二者差异。注意,这不是严格基准测试,但足以说明趋势。

示例 3:性能对比代码

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class ThreadCompareDemo {
    private static final int TASKS = 10_000;

    public static void main(String[] args) throws Exception {
        long platformTime = runPlatformThreads();
        long virtualTime = runVirtualThreads();

        System.out.println("Platform threads cost: " + platformTime + " ms");
        System.out.println("Virtual threads cost:  " + virtualTime + " ms");
    }

    static long runPlatformThreads() throws Exception {
        long start = System.currentTimeMillis();
        try (ExecutorService executor = Executors.newFixedThreadPool(200)) {
            List<java.util.concurrent.Future<?>> futures = new ArrayList<>();
            for (int i = 0; i < TASKS; i++) {
                futures.add(executor.submit(() -> {
                    TimeUnit.MILLISECONDS.sleep(10);
                    return null;
                }));
            }
            for (var f : futures) {
                f.get();
            }
        }
        return System.currentTimeMillis() - start;
    }

    static long runVirtualThreads() throws Exception {
        long start = System.currentTimeMillis();
        try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
            List<java.util.concurrent.Future<?>> futures = new ArrayList<>();
            for (int i = 0; i < TASKS; i++) {
                futures.add(executor.submit(() -> {
                    TimeUnit.MILLISECONDS.sleep(10);
                    return null;
                }));
            }
            for (var f : futures) {
                f.get();
            }
        }
        return System.currentTimeMillis() - start;
    }
}

结果解读

在 I/O 等待占主导的场景下,虚拟线程通常能以更少的资源支撑更高并发。平台线程池会受到线程数量和上下文切换成本影响,而虚拟线程可以更自然地扩展到海量任务。

但要注意:虚拟线程提升的是并发吞吐和资源利用率,不会让 CPU 密集型任务凭空变快。

7. 最佳实践:避免“载体线程阻塞”问题

虚拟线程虽然轻量,但如果你在其中调用了某些会长期占用载体线程的操作,收益会下降。

例如:

  • 在同步块中执行长时间阻塞操作
  • 使用不兼容虚拟线程的老旧 native 调用
  • 依赖线程绑定资源但不做重构

建议:

  • 缩小 synchronized 范围
  • 优先使用更现代的并发工具
  • 对第三方库做兼容性验证

8. 最佳实践:结合结构化并发提升可维护性

Java 21 还带来了结构化并发的预览特性,它和虚拟线程是天然搭档。

当一个请求需要并行调用多个下游服务时,结构化并发可以让任务管理更清晰:

  • 统一启动、统一等待
  • 任一子任务失败可快速取消其他任务
  • 代码层次更清晰,错误处理更集中

这比手写一堆 Future 聚合逻辑更易维护。

9. 落地建议:从一个入口开始改造

如果你正在把传统 Java 服务迁移到虚拟线程,建议按以下步骤推进:

  1. 先挑选 I/O 密集型接口
  2. 使用 newVirtualThreadPerTaskExecutor() 替换旧线程池
  3. 保持同步写法,不急于重构业务逻辑
  4. 加上超时、限流、熔断
  5. 观察 CPU、内存、延迟和下游压力
  6. 再逐步扩展到更多链路

10. 总结

Java 21 虚拟线程的最大价值,不是“替代所有线程池”,而是让高并发服务重新回到更简单、更自然的同步编程模型。

记住这几个关键词:

  • I/O 密集型优先
  • 同步代码更易迁移
  • 配合限流与超时控制
  • 关注下游和载体线程阻塞
  • 结构化并发让复杂任务更可控

如果你想用更低的复杂度获得更高的并发能力,虚拟线程是 Java 21 时代值得优先尝试的技术方案。

到此这篇关于Java 21 虚拟线程最佳实践的文章就介绍到这了,更多相关Java 21 虚拟线程内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • java多线程编程之管道通信详解

    java多线程编程之管道通信详解

    这篇文章主要为大家详细介绍了java多线程编程之线程间的通信,探讨使用管道进行通信,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-10-10
  • Java生成由时间组成的订单号方式

    Java生成由时间组成的订单号方式

    这篇文章主要介绍了Java生成由时间组成的订单号方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-04-04
  • spring中的参数校验技术:jakarta.validation使用详解

    spring中的参数校验技术:jakarta.validation使用详解

    Jakarta Validation 是一个用于校验传递参数的工具,通过注解(如 @NotBlank、@NotNull)在 Spring Boot 框架中可以直接引用依赖进行参数校验,示例展示了如何在登录和获取信息接口中使用这些注解进行参数校验
    2025-11-11
  • Java中基于Shiro,JWT实现微信小程序登录完整例子及实现过程

    Java中基于Shiro,JWT实现微信小程序登录完整例子及实现过程

    这篇文章主要介绍了Java中基于Shiro,JWT实现微信小程序登录完整例子 ,实现了小程序的自定义登陆,将自定义登陆态token返回给小程序作为登陆凭证。需要的朋友可以参考下
    2018-11-11
  • Java(基于Struts2) 分页实现代码

    Java(基于Struts2) 分页实现代码

    这篇文章介绍了Java(基于Struts2) 分页实现代码,有需要的朋友可以参考一下
    2013-10-10
  • Java利用Dijkstra和Floyd分别求取图的最短路径

    Java利用Dijkstra和Floyd分别求取图的最短路径

    本文主要介绍了图的最短路径的概念,并分别利用Dijkstra算法和Floyd算法求取最短路径,最后提供了基于邻接矩阵和邻接表的图对两种算法的Java实现。需要的可以参考一下
    2022-01-01
  • spring cloud gateway 如何修改请求路径Path

    spring cloud gateway 如何修改请求路径Path

    这篇文章主要介绍了spring cloud gateway 修改请求路径Path的操作,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-06-06
  • 详解Java基础之封装

    详解Java基础之封装

    这篇文章主要为大家介绍了Java基础之封装,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助
    2022-01-01
  • tk.mybatis扩展通用接口使用详解

    tk.mybatis扩展通用接口使用详解

    这篇文章主要介绍了tk.mybatis扩展通用接口使用详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-08-08
  • 分布式医疗挂号系统Nacos微服务Feign远程调用数据字典

    分布式医疗挂号系统Nacos微服务Feign远程调用数据字典

    这篇文章主要为大家介绍了分布式医疗挂号系统Nacos微服务Feign远程调用数据字典,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪<BR>
    2022-04-04

最新评论