
围绕 Java 21 虚拟线程的 4 个面试边界,讲清它适合什么负载、为什么不该放进传统线程池,以及 Pinning、ThreadLocal 和资源限流如何判断。
synchronized、监控和框架适配。java.lang.Thread,由 JDK 管理,许多虚拟线程可以在较少的平台线程上运行。它们在阻塞等待网络 I/O 时可以从载体线程卸载,让载体去执行其他虚拟线程。Java 21 将这项能力正式化。2虚拟线程解决的是线程成本高带来的并发规模问题,尤其适合大量任务同时等待网络或数据库结果;它不会降低单个 CPU 任务的计算时间,也不能替代连接池、限流和监控。
它提高的是等待型任务的并发吞吐,保留了同步代码的写法。单次请求的网络延迟、数据库执行时间和 CPU 计算量没有因为换成虚拟线程就自动下降;如果瓶颈在 CPU,应该先看算法、批量计算和处理器利用率。
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
var user = executor.submit(() -> queryUser(userId));
var orders = executor.submit(() -> queryOrders(userId));
return new UserView(user.get(), orders.get());
}newVirtualThreadPerTaskExecutor() 会为每个提交的任务创建一个虚拟线程。JEP 444 的示例就是提交大量等待型任务,并在 try 代码块退出时关闭执行器、等待任务完成。虚拟线程本身很轻量,官方明确建议按任务创建,而不是把虚拟线程放进传统线程池复用。1private final Semaphore permits = new Semaphore(20);
Result callDownstream(Request request) throws Exception {
permits.acquire();
try {
return client.call(request);
} finally {
permits.release();
}
}Semaphore、阻塞队列等基于 LockSupport 的工具列为可以在虚拟线程中正常挂起的并发构件。线程模型负责承载任务,信号量负责保护稀缺资源,别用一个「虚拟线程池大小」同时表达两件事。1synchronized 方法或代码块里,或者进入 native / foreign function。虚拟线程被 pin 住时,如果又在等待 I/O,载体线程也会一起被占住;频繁、长时间发生会损害扩展能力。1synchronized 就一定有问题」。更准确的判断是:synchronized 区域,可以评估显式锁,并保持临界区短。JEP 444 建议用 JFR 的 pinned 事件,或在 Java 21 中用 -Djdk.tracePinnedThreads=full 获取堆栈,再决定是否修改;不要凭感觉全局替换锁。1我不会把 Pinning 当成语法错误。先看阻塞点是否在synchronized或 native 调用里,再用 JFR 或 pinned thread trace 看持续时间和频率;只有热点路径把长等待包在锁里,才需要调整锁的边界。
ThreadLocal,所以旧代码通常可以运行。但传统平台线程池里常见的一种做法是:在线程第一次执行任务时创建昂贵资源,放进 ThreadLocal,后面的任务复用同一线程里的资源。迁移到「每任务一个虚拟线程」后,每个任务可能拿到自己的 ThreadLocal,连接、客户端或缓存的创建次数会跟着放大。JEP 444 特别提醒,不要用 ThreadLocal 把昂贵资源绑定到共享线程的复用逻辑上。1我先确认任务是不是 I/O 等待型,以及目标是提高吞吐还是降低单次延迟。若请求主要等待 HTTP 或数据库,我会用 Java 21 的newVirtualThreadPerTaskExecutor()按任务创建虚拟线程,不把虚拟线程再放进传统线程池。下游连接数和 QPS 仍用连接池、信号量或客户端限流保护。然后检查 Java 21 下热点路径是否有synchronized包住长 I/O,用 JFR 或 pinned trace 验证 Pinning。最后清理依赖平台线程复用的ThreadLocal资源缓存,补上关闭、归还和上下文传递测试。若任务是 CPU 密集型,先优化计算和核心利用率,不能指望换线程模型解决瓶颈。
ThreadLocal 只是请求上下文,哪些实际上在偷偷缓存昂贵资源?CompletableFuture 吗?CompletableFuture 是异步组合 API。先看现有代码的可读性、库的阻塞行为和监控方式,再决定迁移,不要按「新旧」二选一。ThreadLocal,回答就有边界了。openjdk.org
dev.java
This story was produced automatically by a channel. One sentence is all it takes for Neodrop to keep producing for you.