跳到主要内容

JDK21 虚拟线程 VS 传统线程

· 阅读需 5 分钟

JDK21 虚拟线程 VS 传统线程 - 向着朝阳 - 博客园

JDK21虚拟线程的定义与核心特性

轻量级实现‌:虚拟线程(Virtual Threads)由JVM管理,与操作系统线程解耦,单应用可创建数百万个线程,解决了传统线程的资源限制问题。‌

核心优势对比‌:

  • 资源消耗低‌:创建和销毁开销仅为平台线程的1/1000,适用于高并发场景。‌

  • 高效调度‌:通过挂起机制减少上下文切换,I/O阻塞时自动释放载体线程(Carrier Thread)。‌

  • 简化编程‌:语法与传统线程兼容,无需学习复杂并发框架。‌

JDK21虚拟线程的优势

使用虚拟线程(Virtual Threads)后,并不意味着可以完全抛弃传统线程池。二者各有适用场景,在实际开发中更可能是互补关系,而非替代关系。

特性平台线程虚拟线程
内存开销约1MB/线程初始4KB,弹性扩展
上下文切换内核级,成本高用户级,成本极低
阻塞处理线程挂起,无法复用自动挂起/恢复,资源可复用
调度方式操作系统抢占式调度JVM协作式调度
适用场景CPU密集型任务IO密集型任务

为什么不能完全抛弃传统线程池?

1 CPU密集型任务仍需传统线程池

虚拟线程的优势体现在I/O密集型任务(线程大部分时间在等待I/O,而非占用CPU)。但对于CPU密集型任务(如复杂计算、数据处理):

  • 此时线程会持续占用CPU,虚拟线程无法通过“挂起”释放资源(因为本质上还是需要OS线程执行计算)
  • 过多的虚拟线程同时执行CPU密集型任务,会导致CPU上下文切换频繁,反而降低效率
  • 传统线程池(如FixedThreadPool)通过限制线程数量(通常设为CPU核心数),可避免CPU过载,更适合此类场景
2 资源隔离仍需传统线程池

传统线程池的核心价值之一是资源隔离(通过不同线程池隔离不同业务/任务):

  • 例如:用一个线程池处理核心业务(如支付),另一个线程池处理非核心业务(如日志上报),避免非核心任务耗尽资源影响核心业务
  • 虚拟线程池(如Executors.newVirtualThreadPerTaskExecutor())通常是全局共享的,难以实现精细化的资源隔离
  • 复杂系统中,仍需传统线程池来控制特定任务的资源使用上限(如最大并发数、队列长度)
3 兼容性与迁移成本
  • 现有系统中大量代码依赖传统线程池的特性(如ThreadPoolExecutor的拒绝策略、监控指标、线程命名规则等)
  • 虚拟线程虽兼容Thread API,但与传统线程池的部分设计理念(如“池化复用”)不同,直接替换可能引入风险
  • 混合使用两种线程模型(关键路径用虚拟线程提效,核心任务用传统线程池保稳定)是更务实的选择

典型混合使用案例

以一个电商系统为例:


// 1. 虚拟线程池:处理高并发I/O任务(如HTTP请求、数据库查询)
ExecutorService virtualExecutor = Executors.newVirtualThreadPerTaskExecutor();

// 2. 传统线程池:处理CPU密集型任务(如订单金额计算、库存扣减)
ExecutorService cpuExecutor = new ThreadPoolExecutor(
***// 省略
);

// 处理用户下单请求(I/O密集型,用虚拟线程)
virtualExecutor.submit(() -> {
// 步骤1:查询商品信息(数据库I/O)
Product product = productDao.query(productId);

// 步骤2:计算订单金额(CPU密集型,提交给传统线程池)
BigDecimal amount = cpuExecutor.submit(() ->
orderCalculator.calculate(product, quantity)
).get();

// 步骤3:创建订单(数据库I/O)
orderDao.create(new Order(productId, amount));
});

分析
  • submit() 本身是非阻塞的:它会立即返回一个 Future<BigDecimal>
  • .get() 是阻塞方法:它会一直等待,直到后台的 CPU 任务执行完毕并返回结果。
  • 因为这段代码运行在 虚拟线程 中(由 virtualExecutor 提交的任务),所以:
  • 虚拟线程会被 挂起(park),但底层的 载体线程(carrier thread)不会被阻塞(这是虚拟线程的优势)。
  • 从 JVM 调度角度看,这是“轻量级阻塞”,性能开销小。
  • 但从逻辑上讲,当前任务(下单流程)在步骤2是同步等待结果的,后续步骤(如创建订单)必须等金额计算完成后才能继续。

总结

虚拟线程是对Java并发模型的重要补充,而非替代传统线程池。在实际开发中:

  • I/O密集型高并发场景:优先使用虚拟线程提高吞吐量
  • CPU密集型或需要资源隔离的场景:继续使用传统线程池
  • 大多数系统会混合使用两种模型,充分发挥各自优势

核心原则是:根据任务类型(I/O密集/CPU密集)和资源需求(高并发/隔离控制)选择合适的线程模型,而非盲目替换。

⚠️注意

JDK21虚拟线程有个弊端,synchronized修饰的方法和synchronized块要注意,不能长时间阻塞频繁调用。 如果需长时间阻塞和频繁调用,必须使用ReentrantLock来替换synchronized写法。

  • 错误的
synchronized(lockObj) {
frequentIO();
}
  • 正确的
private final ReentrantLock lock = new ReentrantLock();

lock.lock();
try {
frequentIO();
} finally {
lock.unlock();
}

参考原文:

JDK21中,虚拟线程能够完全代替线程吗? - Aray的回答 - 知乎