bigywater.tech
Java · 154

虚拟线程:Java 21 如何让一个线程对应一个任务

虚拟线程让「一个任务一个线程」重新可行,但它到底改变了什么?这篇用 JDK 21 在 macOS 上量出线程数上限、耗时、内存和纯计算任务的差别(pinning 在下一篇)。

1073 ms
10000 个 sleep 1 秒的任务用虚拟线程跑完的总耗时
第 4059 个
每任务一个平台线程时,创建线程失败的任务序号
868 vs 855 ms
纯计算任务:虚拟线程与平台线程几乎一样

虚拟线程与载体线程

实测 · 本机执行输出
  虚拟线程:isVirtual=true,isDaemon=true,名字=""(默认空),优先级=5
  平台线程:isVirtual=false,isDaemon=false,名字="Thread-0"
  虚拟线程里 Thread.currentThread() 的 toString = VirtualThread[#N]/runnable@ForkJoinPool-1-worker-K
  2000 个虚拟线程(每个先 sleep 50 ms 再继续),一共在 10 个不同的载体线程上运行过(机器核数 10)

一万个阻塞任务

实测 · 本机执行输出
  虚拟线程                   10000 个任务各 sleep 1 秒:0.5 秒时 RSS 85 MB,总耗时 1073 ms,完成 10000 个
[1.309s][warning][os,thread] Failed to start thread "Unknown thread" - pthread_create failed (EAGAIN) for attributes: stacksize: 2048k, guardsize: 16k, detached.
[1.309s][warning][os,thread] Failed to start the native thread for java.lang.Thread "pool-1-thread-4059"
  平台线程(每个任务一个)           10000 个任务各 sleep 1 秒:提交到第 4059 个任务时失败:OutOfMemoryError(unable to create native thread);0.5 秒时 RSS 281 MB,总耗时 1227 ms,完成 4058 个
  固定 200 个线程的线程池         10000 个任务各 sleep 1 秒:0.5 秒时 RSS 184 MB,总耗时 50358 ms,完成 10000 个

创建开销与纯计算

实测 · 本机执行输出
  创建 + 启动 + join 一个线程的平均耗时:虚拟线程 6 µs(100000 个),平台线程 36 µs(10000 个)
  20 个纯计算任务(核数的 2 倍):虚拟线程 总耗时 868 ms
  20 个纯计算任务(核数的 2 倍):平台线程 总耗时 855 ms

分步演示

虚拟线程
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK(Temurin 17.0.20、Homebrew 21.0.12 与 27,每篇注明实际用的版本);用 javac 编译、java 直接运行,需要第三方库时用 Maven 解析依赖;计时用 System.nanoTime,数字每次运行会有小幅抖动。JDK 21.0.12,10 核 macOS(平台线程数上限是这台机器的,约 4000 个)
    • 没有验证:JDK 17 没有虚拟线程、JDK 27 上的变化、结构化并发与 ScopedValue(后面的篇目)、虚拟线程的栈增长与深递归、与 Netty/Reactor 等异步框架的吞吐对比、真实 I/O(只用了 sleep 代替);平台线程数上限因系统而异
    京ICP备2026067502号-1京公网安备11010502063487号