Java 21虚拟线程执行器为何性能不及ForkJoinPool?
MDC的ThreadLocal拷贝开销
MDC基于ThreadLocal实现上下文存储,而虚拟线程在调度到不同载体线程时,ThreadLocal的内容会被完整拷贝。你的场景中每次任务都需要设置MDC,大量虚拟线程的挂载、卸载动作会将这个拷贝成本放大。反观ForkJoinPool使用固定30个OS线程,ThreadLocal属于线程本身,没有频繁拷贝的额外开销。HTTP客户端未适配虚拟线程
如果使用的HTTP客户端(如老版本RestTemplate、Apache HttpClient)未针对虚拟线程做优化,底层IO操作可能仍会阻塞载体线程,而非触发虚拟线程主动park以释放载体。这种情况下虚拟线程的核心优势(IO阻塞时释放载体线程)完全无法发挥,反而要承担虚拟线程调度的额外成本,性能自然不如直接使用OS线程的ForkJoinPool。虚拟线程调度与创建的额外开销
Executors.newVirtualThreadPerTaskExecutor()会为每个任务创建新的虚拟线程,虽然虚拟线程创建成本远低于OS线程,但频繁的创建、销毁仍会产生累积开销。而ForkJoinPool是固定大小的线程池,线程复用无创建成本。此外,如果虚拟线程因IO未正确释放载体,调度器需要处理更多线程切换,其调度成本也会高于固定OS线程的调度逻辑。Spring MDC上下文传递的适配开销
即便开启了spring.threads.virtual.enabled=true,Spring对虚拟线程的MDC上下文传递可能存在额外开销:异步任务提交时,Spring需要将请求线程的MDC上下文拷贝到虚拟线程,该操作在虚拟线程场景下的成本比OS线程更高。若你的代码中还要手动设置MDC,等于进行双重拷贝,进一步增加了耗时。
内容的提问来源于stack exchange,提问作者condiorno

