跳转至

虚拟线程(Virtual Threads)#

版本信息

  • 最低 JDK:21(预览 19/20 → 21 正式)
  • JEPJEP 444

为什么需要#

JDK 8 里要写高并发「每个请求一个线程」的代码,平台线程昂贵(~1MB 栈),上千个就吃光内存,于是被迫用异步回调(CompletableFuture)/ 响应式框架,代码可读性骤降。虚拟线程让你用同步阻塞的写法达到异步的吞吐,单 JVM 可轻松承载数十万级虚拟线程(远超平台线程上限;具体取决于堆大小与栈深度)。

语法#

package com.javamodern.vthread;

public class VirtualThreadDemo {
    // 创建并启动一个虚拟线程(封装 Thread.ofVirtual,便于演示与测试)
    public static Thread startVirtualThread(String name, Runnable task) { // (1)
        return Thread.ofVirtual().name(name).start(task);
    }

    public static void main(String[] args) throws InterruptedException {
        Thread vt = startVirtualThread("my-virtual", () ->
                System.out.println("running on " + Thread.currentThread()));
        vt.join();
        System.out.println("isVirtual = " + vt.isVirtual());
    }
}
  1. :material-lightbulb: startVirtualThread 封装 Thread.ofVirtual() 创建虚拟线程,API 与平台线程一致,学习成本几乎为零。

与 JDK 8 旧写法对比#

Thread t = new Thread(() -> { /* 阻塞 IO */ });
t.start();
Thread vt = Thread.ofVirtual().start(() -> { /* 阻塞 IO */ });

底层原理#

虚拟线程是用户态轻量线程,挂载(mount)到少量载体平台线程(ForkJoinPool,数量默认等于可用 CPU 核数)上运行,阻塞时被卸载(unmount),载体线程立即去跑别的虚拟线程(M:N 调度):

flowchart TD
    A["Thread.ofVirtual().start(task)"] --> B["挂载到载体线程 ForkJoinPool"]
    B --> C{"task 阻塞?"}
    C -- 否 --> D["运行至结束"]
    C -- 是 --> E["卸载 unmount<br/>载体线程立即释放"]
    E --> F["阻塞条件就绪"]
    F --> B
    style A fill:#bbdefb
    style E fill:#ffe0b2

常见坑 / 最佳实践#

  • 不要池化虚拟线程:用完即弃,用 Executors.newVirtualThreadPerTaskExecutor() 提交。
  • synchronized 钉住(pinning)载体线程:JDK 21 中 synchronized 内阻塞会钉住载体,应用 ReentrantLock 替代。
  • 虚拟线程适合 IO 密集,不适合 CPU 密集(不增加 CPU 核数)。

小结#

虚拟线程让「同步写法 + 高吞吐」兼得,是 JDK 8→21 并发模型最大的一次跃迁。

参考#