Java 25虚拟线程使用实例

在 Java 25 中,虚拟线程已作为一项成熟稳定的特性被正式引入,其核心目标在于简化高并发应用程序的编写,尤其擅长处理大量 I/O 密集型任务。相较于传统平台线程,虚拟线程的创建和上下文切换成本极低,允许开发者以"一个请求一个线程"的同步编程模型来构建高吞吐量的服务。

基础创建与使用

虚拟线程的创建主要有三种方式:Thread.ofVirtual()Thread.startVirtualThread() 以及通过 Executors.newVirtualThreadPerTaskExecutor()

方式一:使用 Thread.startVirtualThread(Runnable task)

这是启动一个虚拟线程最简单直接的方法。

java 复制代码
public class BasicVirtualThreadDemo {
    public static void main(String[] args) throws InterruptedException {
        // 启动一个虚拟线程执行任务
        Thread vThread = Thread.startVirtualThread(() -> {
            System.out.println("Hello from a virtual thread!");
            System.out.println("Thread name: " + Thread.currentThread().getName());
            System.out.println("Is virtual: " + Thread.currentThread().isVirtual());
        });

        // 等待虚拟线程执行完毕
        vThread.join();
    }
}

运行上述代码,输出会显示线程名称以 VirtualThread 开头,并且 isVirtual() 方法返回 true

方式二:使用 Thread.ofVirtual().start(Runnable task)

Thread.ofVirtual() 返回一个 Thread.Builder,提供了更丰富的线程配置选项。

java 复制代码
public class BuilderVirtualThreadDemo {
    public static void main(String[] args) {
        // 使用Builder创建并启动虚拟线程,可以设置线程名称
        Thread.Builder builder = Thread.ofVirtual().name("my-vthread-", 0);
        Thread vThread = builder.start(() -> {
            System.out.println("Task executed by: " + Thread.currentThread().getName());
            try {
                Thread.sleep(1000); // 模拟I/O阻塞操作
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Task completed.");
        });

        try {
            vThread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

方式三:使用 Executors.newVirtualThreadPerTaskExecutor()

对于需要管理多个并发任务的场景,使用 ExecutorService 是更佳实践。Java 25 提供了专门的执行器服务来管理虚拟线程。

java 复制代码
import java.util.concurrent.*;

public class ExecutorVirtualThreadDemo {
    public static void main(String[] args) {
        // 创建使用虚拟线程的执行器
        try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
            // 提交多个任务
            Future<String> future1 = executor.submit(() -> {
                Thread.sleep(500);
                return "Result from task 1";
            });
            Future<Integer> future2 = executor.submit(() -> {
                Thread.sleep(300);
                return 42;
            });

            // 获取任务结果
            System.out.println(future1.get()); // 输出: Result from task 1
            System.out.println(future2.get()); // 输出: 42
        } catch (ExecutionException | InterruptedException e) {
            e.printStackTrace();
        }
        // try-with-resources 会自动关闭ExecutorService
    }
}

这种执行器会为每个提交的任务创建一个新的虚拟线程,非常适合处理大量短生命周期的异步任务 。

与结构化并发结合使用

Java 25 中,虚拟线程与结构化并发(StructuredTaskScope)的结合是处理复杂并发流程的推荐模式。结构化并发强制要求任务在明确的作用域内创建和完成,简化了错误处理和资源管理 。

java 复制代码
import java.util.concurrent.*;
import java.util.concurrent.StructuredTaskScope.*;

public class StructuredConcurrencyDemo {
    public static void main(String[] args) throws Exception {
        // 使用结构化并发作用域处理并发任务
        String result = handleUserRequest();
        System.out.println("Final result: " + result);
    }

    static String handleUserRequest() throws Exception {
        try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
            // 在作用域内派生子任务(每个子任务运行在独立的虚拟线程上)
            Subtask<String> userSubtask = scope.fork(() -> fetchUserInfo("user123"));
            Subtask<Integer> orderSubtask = scope.fork(() -> fetchOrderCount("user123"));

            // 等待所有子任务完成或任一失败
            scope.join();
            scope.throwIfFailed(); // 如果任何子任务失败,抛出异常

            // 组合结果
            String userInfo = userSubtask.get();
            int orderCount = orderSubtask.get();
            return String.format("User: %s, Orders: %d", userInfo, orderCount);
        }
    }

    static String fetchUserInfo(String userId) throws InterruptedException {
        Thread.sleep(200); // 模拟网络I/O
        return "Alice";
    }

    static Integer fetchOrderCount(String userId) throws InterruptedException {
        Thread.sleep(150); // 模拟数据库I/O
        return 5;
    }
}

在这个例子中,fetchUserInfofetchOrderCount 两个 I/O 操作并发执行,每个都在自己的虚拟线程中运行。StructuredTaskScope 确保了两个任务要么都成功完成,要么在发生错误时一起被取消,避免了资源泄漏 。

处理阻塞操作与性能对比

虚拟线程的关键优势在于处理阻塞操作(如 I/O、锁等待)时的高效性。当虚拟线程发生阻塞时,JVM 会将其挂起,并释放其载体线程(一个真正的平台线程)去执行其他就绪的虚拟线程,从而用极少的硬件线程支撑海量并发。

java 复制代码
import java.util.concurrent.*;

public class IOBoundTaskDemo {
    public static void main(String[] args) {
        int taskCount = 10_000;
        System.out.println("模拟 " + taskCount + " 个I/O密集型任务...");

        long startTime = System.currentTimeMillis();

        try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
            // 提交大量模拟I/O阻塞的任务
            for (int i = 0; i < taskCount; i++) {
                final int taskId = i;
                executor.submit(() -> {
                    try {
                        // 模拟一个耗时的I/O操作,如数据库查询或HTTP调用
                        Thread.sleep(10);
                        // System.out.println("Task " + taskId + " completed on " + Thread.currentThread().getName());
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                    }
                });
            }
        } // ExecutorService 自动关闭,会等待所有任务完成

        long duration = System.currentTimeMillis() - startTime;
        System.out.println("所有任务完成,总耗时: " + duration + " ms");
    }
}

如果使用传统的固定大小线程池(例如 Executors.newFixedThreadPool(200))来处理上万个阻塞任务,由于线程数量有限,大部分任务将排队等待,总耗时会显著增加。而虚拟线程方案可以几乎同时启动所有任务,总耗时主要取决于单个任务的阻塞时间(约10ms),而非任务数量,从而极大提升了吞吐量 。

重要注意事项与最佳实践

  1. 线程局部变量 :虚拟线程支持 ThreadLocal,但由于可以创建海量虚拟线程,需谨慎使用以避免内存消耗过大。对于大量可复用的上下文信息,考虑使用 ScopedValue(Java 20+ 引入的预览特性,在 Java 25 中可能已稳定或继续演进)。
  2. 线程池 :不要将虚拟线程池化。虚拟线程的设计初衷就是廉价、可随意创建和销毁。应始终使用 Executors.newVirtualThreadPerTaskExecutor() 或类似机制,而不是尝试复用虚拟线程对象 。
  3. 同步操作 :在虚拟线程内部执行 synchronized 块或 ReentrantLock.lock() 等同步操作时,会阻塞其载体线程。如果同步操作频繁或耗时,可能会抵消虚拟线程的优势。在关键路径上,优先考虑使用 java.util.concurrent 包中的并发工具(如 ReentrantLock 配合 tryLock)或异步 API 。
  4. 调试与监控 :Java 25 增强了虚拟线程的调试和监控能力。可以使用 jcmd <pid> Thread.dump_to_file -format=json <file> 生成包含虚拟线程的线程转储,或通过 JMX 的 java.lang.management.ThreadMXBean 来监控虚拟线程的状态 。

启用虚拟线程

在 Java 25 中,虚拟线程是默认启用的核心特性,无需特殊 JVM 参数即可使用 。对于从早期预览版升级的项目,只需确保移除了相关的 --enable-preview 标志即可。


参考来源

相关推荐
一条泥憨鱼1 小时前
苍穹外卖【day3|菜品管理】
java·数据库·sql·mysql·mybatis
Wenzar_1 小时前
Playwright 实战:高可信 UI 回归验证流水线
java·ui
云烟成雨TD1 小时前
Spring AI 1.x 系列【43】基于标准输入输出 (STDIO) 与服务端推送事件 (SSE) 的 MCP 服务端
java·人工智能·spring
va学弟1 小时前
Java 网络通信编程(9):从 BIO 到 NIO
java·运维·服务器·网络
凡人叶枫1 小时前
Effective C++ 条款05:了解 C++ 默默编写并调用哪些函数
java·linux·开发语言·c++·effective c++·编程范式
Full Stack Developme1 小时前
G1回收器的工作机制
java·jvm
砍材农夫1 小时前
物联网实战:Spring Boot + Netty 搭建 MQTT平台 | 多协议适配与模块化设计
java·spring boot·后端·物联网·spring
云烟成雨TD1 小时前
Spring AI 1.x 系列【41】接入高德 MCP 服务
java·人工智能·spring
winlife_2 小时前
全程用 AI 做一款商业级手游 · EP7 表现层与手感:从“能跑“到“摸起来爽“
java·开发语言·人工智能·unity·ai编程·游戏开发·mcp