← 每日面试题 学习看板

JDK21中的虚拟线程是怎么回事?

面试题/00java/02Java 并发编程/01线程基础概念/JDK21中的虚拟线程是怎么回事?.md

面试记录 2026-10-26

题目:JDK21 中的虚拟线程是怎么回事?

难度:medium

标签:java/thread, general/knowledge, difficulty/medium

评分:⭐⭐⭐☆☆(3/5)

我的回答

jdk21 的虚拟线程是一种简化线程的资源实现,不像线程是操作系统管控的,虚拟线程是由 jvm 管控的轻量级线程;执行的时候,并不像是线程和操作系统线程一对一,而是大量的虚拟线程对应少量的操作系统线程去执行任务。

点评

优点:

不足:

完整讲解(权威版)

最后更新:2026-10-26 · 基于第 1 次作答

L1 · 30 秒速记

结论:虚拟线程 = JVM 层 M:N 调度,靠 Continuation(可挂起栈)实现,IO 密集下能开百万级并发。 关键词:Continuation · M:N 调度 · pinning 陷阱 · IO 密集适用 边界:CPU 密集无收益;synchronized 块内阻塞、JNI 会 pin carrier thread

L2 · 核心讲解

核心考点定位:能否说清虚拟线程的底层机制(Continuation)、M:N 调度模型、适用场景与 pinning 陷阱。

机制链(3 环):

  1. 传统线程 = 1:1 映射 OS 线程,创建成本 ~1MB 栈 → 大规模并发受限于 OS 线程数上限(数万级)
  2. 虚拟线程通过 Continuation 实现挂起:阻塞时把整个栈帧保存到堆内存,释放 carrier thread 干别的 → 唤醒时把栈帧还原到另一个 carrier thread 继续执行
  3. M:N 调度:大量虚拟线程(百万级)共享少量 OS 线程(carrier threads,默认 CPU 核数)→ JVM ForkJoinPool 负责调度;栈是可变大小的(32-512B 起,最大 1MB)

证据锚点(去重 ≥10):

对比与权衡:

维度OS 线程虚拟线程
映射模型1:1M:N
栈大小固定 1MB可变 32B-1MB
并发量级数万百万级
调度者操作系统JVM ForkJoinPool
创建成本高(系统调用)低(用户态)
阻塞代价独占 OS 线程挂起后 carrier 释放
pinning无存在(synchronized 阻塞 / JNI)
适用CPU 密集IO 密集

常见误区:

  1. 错误:"虚拟线程可以无限开" → 正确:百万级是量级参考,但每个栈仍需内存;-Djdk.virtualThreadScheduler.stackSize 可限制上限 → 为什么:栈是可变大小但仍有上限
  2. 错误:"虚拟线程能替代所有线程池" → 正确:CPU 密集场景无收益(还是 CPU 核数决定吞吐),IO 密集才适用 → 为什么:虚拟线程优势在阻塞时释放 carrier,CPU 密集不阻塞
  3. 错误:"synchronized 在虚拟线程下完全不能用" → 正确:纯计算不 pin,只是别在 synchronized 块里阻塞 → 为什么:pin 发生在阻塞时,不是持有锁时
  4. 错误:"虚拟线程栈和 OS 线程栈一样大" → 正确:虚拟线程栈可变(32B-1MB),OS 线程固定 1MB → 为什么:可变栈是百万级并发的关键

适用边界:

延伸追问(≥3)

  1. 虚拟线程和 Go goroutine 有什么本质区别?
  1. 虚拟线程下 Spring 事务、JPA 需要改造吗?
  1. 怎么排查 pinning?
  1. 虚拟线程会替代线程池吗?

面试者参考回答(400–700 字)

【结论】 JDK 21 虚拟线程本质是 JVM 层 M:N 调度模型——大量虚拟线程共享少量 OS 线程(carrier threads),靠 Continuation 实现挂起。

【三点】

第一,底层机制:虚拟线程通过 Continuation 实现——可挂起的调用栈。阻塞时把栈帧保存到堆内存,释放 carrier thread 执行其他虚拟线程;唤醒时把栈帧还原到另一个 carrier thread 继续。栈是可变大小(32-512B 起,最大 1MB),所以能开百万级虚拟线程。

第二,关键陷阱是 pinning:虚拟线程在 synchronized 块里阻塞(IO、sleep、join)或调用 native 方法(JNI)时,carrier thread 会被固定在这个虚拟线程上无法卸载,导致 M:N 退化。JDK 21 用 ReentrantLock 缓解——uncontained 时不 pin。排查靠 -Djdk.tracePinnedThreads=full 或 JFR jdk.VirtualThreadPinned 事件。

第三,使用边界:适合 IO 密集(HTTP、DB、RPC),一个请求一个虚拟线程百万并发轻松扛;不适合 CPU 密集(无收益);连接池要跟着调,HikariCP 默认 max=CPU*10 需拉高到几千。API 直接用 Thread.ofVirtual().start() 或 Executors.newVirtualThreadPerTaskExecutor(),Spring Boot 3.2+ 有官方支持。

【边界】 注意两点:一是虚拟线程不是万能的,CPU 密集场景线程池仍是最佳选择;二是生产部署要监控 pinning 事件,避免"看似用了虚拟线程,实际 carrier 被固定"的退化。


作答历史

日期评分备注
2026-10-26⭐⭐⭐☆☆首次作答