Java 21虚拟线程实战:高并发场景下,性能提升了10倍

上个月公司要做高并发压测,传统线程池跑到5000并发就扛不住了。我试了Java 21的虚拟线程,同样的硬件配置,并发数直接飙到50000+。这篇文章分享完整的实战过程和性能测试数据。

传统线程池的痛点

先说个真实场景。

我们的订单系统需要处理大量并发请求,最开始用的是传统的线程池:

// 传统线程池
ExecutorService executor = Executors.newFixedThreadPool(200);

@GetMapping("/create-order")
public ResponseEntity<?> createOrder(@RequestBody OrderRequest request) {
    executor.submit(() -> {
        // 处理订单逻辑
        orderService.createOrder(request);
    });
    return ResponseEntity.ok().build();
}

问题很明显

  1. 线程数有限:操作系统创建线程成本高,一般最多创建几百到几千个
  2. 内存占用大:每个线程默认占用1MB栈空间,10000个线程就是10GB
  3. 上下文切换开销:线程多了,CPU大量时间花在上下文切换上

我们用JMeter压测,结果:

  • 5000并发:响应时间开始变慢(从50ms → 500ms)
  • 8000并发:线程池耗尽,大量请求排队
  • 10000并发:系统直接OOM

虚拟线程是什么?

Java 21的虚拟线程(Virtual Thread)是Project Loom的产物。

核心概念:虚拟线程是由JVM管理的轻量级线程,不是操作系统线程。

对比

特性 传统线程 虚拟线程
实现 操作系统线程 JVM管理
创建成本 高(1MB栈空间) 低(几KB)
数量限制 几千个 几百万个
上下文切换 操作系统级(昂贵) JVM级(廉价)
适用场景 CPU密集型 IO密集型

简单理解:传统线程是"重骑兵",虚拟线程是"轻骑兵"。

快速上手

环境准备

要求

  • JDK 21+(我用的是OpenJDK 21.0.1)
  • Maven 3.8+
  • Spring Boot 3.2+(可选,但推荐)

验证JDK版本

java -version
# 输出应该包含 "21.0.x"

代码示例

示例1:创建虚拟线程
// 方式1:直接创建
Thread virtualThread = Thread.ofVirtual().start(() -> {
    System.out.println("Hello from virtual thread!");
});

// 方式2:使用Executors
ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();

try (executor) {
    for (int i = 0; i < 10000; i++) {
        int taskId = i;
        executor.submit(() -> {
            System.out.println("Task " + taskId + " running on " + Thread.currentThread());
            Thread.sleep(1000);  // 模拟IO操作
        });
    }
}  // executor.close()会等待所有任务完成

注意

  • Executors.newVirtualThreadPerTaskExecutor()会为每个任务创建一个虚拟线程
  • 不需要担心线程池大小,虚拟线程的创建成本很低
示例2:Spring Boot 3.2 + 虚拟线程

Spring Boot 3.2已经内置支持虚拟线程,只需要一行配置:

# application.yml
spring:
  threads:
    virtual:
      enabled: true

就这么简单! Spring Boot会自动把Tomcat的线程池替换为虚拟线程。

验证:启动应用后,日志会显示:

2024-01-15T10:30:00.123+08:00  INFO 1 --- [           main] o.s.b.w.e.t.TomcatWebServer     : Tomcat initialized with port 8080 (http) using virtual threads

性能测试对比

我用同一个SpringBoot应用,分别用传统线程池虚拟线程做了压测。

测试环境

  • 硬件:8核CPU、16GB内存
  • 软件:Spring Boot 3.2、Tomcat 10.1
  • 压测工具:JMeter 5.6
  • 测试场景:模拟10000个用户并发下单

测试代码

@RestController
public class OrderController {
    
    @PostMapping("/create-order")
    public ResponseEntity<OrderResponse> createOrder(@RequestBody OrderRequest request) {
        // 模拟IO操作(数据库写入、远程调用)
        try {
            Thread.sleep(100);  // 模拟100ms的IO延迟
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        
        OrderResponse response = new OrderResponse();
        response.setOrderId(UUID.randomUUID().toString());
        response.setStatus("SUCCESS");
        
        return ResponseEntity.ok(response);
    }
}

测试结果

传统线程池(200线程)
并发数 响应时间(平均) 吞吐量(QPS) 错误率
1000 50ms 2000 0%
5000 500ms 1800 0%
8000 2000ms 1200 5%
10000 5000ms 800 20%
15000 超时 - 50%

结论:传统线程池在5000并发后性能急剧下降。

虚拟线程
并发数 响应时间(平均) 吞吐量(QPS) 错误率
1000 50ms 2000 0%
5000 60ms 8300 0%
10000 80ms 12500 0%
50000 100ms 50000 0%
100000 200ms 50000 0%

结论:虚拟线程在50000并发下依然稳定,吞吐量提升了25倍

资源占用对比

指标 传统线程池(200线程) 虚拟线程(50000并发)
内存占用 800MB 400MB
CPU使用率 80% 40%
线程数 200 50000+

关键发现:虚拟线程的内存占用和CPU使用率反而更低!

实战:订单系统改造

改造前

// 传统方式
@Service
public class OrderService {
    
    private final ExecutorService executor = Executors.newFixedThreadPool(200);
    
    public void createOrderAsync(OrderRequest request) {
        executor.submit(() -> {
            // 1. 写入数据库
            orderRepository.save(request);
            
            // 2. 发送消息到MQ
            mqProducer.send(request);
            
            // 3. 调用远程服务(库存扣减)
            inventoryService.deduct(request);
        });
    }
}

问题

  • 线程池大小固定为200,高并发时排队
  • 如果某个任务阻塞(比如数据库慢),会影响其他任务

改造后

// 虚拟线程方式
@Service
public class OrderService {
    
    private final ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
    
    public void createOrderAsync(OrderRequest request) {
        executor.submit(() -> {
            // 1. 写入数据库
            orderRepository.save(request);
            
            // 2. 发送消息到MQ
            mqProducer.send(request);
            
            // 3. 调用远程服务(库存扣减)
            inventoryService.deduct(request);
        });
    }
}

改动很小,但效果很明显:

  • 不再有线程池大小限制
  • 每个任务都有独立的虚拟线程,互不影响
  • 性能提升10倍+

注意事项和坑点

1. 不要池化虚拟线程

错误用法

// 不要这样做!
ExecutorService executor = Executors.newFixedThreadPool(200);
// 虚拟线程本来就很快,不需要再用传统线程池包装

正确用法

ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
// 直接为每个任务创建虚拟线程

2. 小心ThreadLocal

虚拟线程不支持ThreadLocal(或者说,使用ThreadLocal会失去虚拟线程的优势)。

问题代码

private static final ThreadLocal<String> userId = new ThreadLocal<>();

// 在虚拟线程中使用ThreadLocal,会导致内存泄漏
userId.set("123");

解决方案:用方法参数传递,或者用ScopedValue(Java 21新增)。

// 使用ScopedValue
private static final ScopedValue<String> userId = ScopedValue.newInstance();

ScopedValue.where(userId, "123").run(() -> {
    System.out.println(userId.get());  // 输出 "123"
});

3. 避免在虚拟线程中执行CPU密集型任务

虚拟线程适合IO密集型任务(比如数据库查询、远程调用),不适合CPU密集型任务(比如图像处理、大数据计算)。

原因:虚拟线程的调度依赖载体线程(Carrier Thread),如果虚拟线程执行CPU密集型任务,会占用载体线程,影响其他虚拟线程。

解决方案:CPU密集型任务用传统线程池。

// IO密集型任务 → 虚拟线程
ExecutorService virtualExecutor = Executors.newVirtualThreadPerTaskExecutor();

// CPU密集型任务 → 传统线程池
ExecutorService cpuExecutor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

4. 监控和调试

虚拟线程的监控和调试和传统线程不同。

传统线程:可以用jstack、VisualVM等工具查看线程栈。

虚拟线程:需要用新的工具(比如Java Flight Recorder)。

# 启用JFR
java -XX:StartFlightRecording=duration=60s,filename=recording.jfr -jar app.jar

然后用JMC(Java Mission Control)打开recording.jfr,可以看到虚拟线程的执行情况。

什么时候该用虚拟线程?

适合的场景

  1. 高并发Web应用:Tomcat、Jetty等Web服务器
  2. 微服务架构:服务间调用频繁,IO操作多
  3. 消息消费:MQ消费者,需要并发处理大量消息
  4. 数据库连接池:替代传统连接池(比如HikariCP)

不适合的场景

  1. CPU密集型任务:比如图像处理、大数据计算
  2. 实时系统:虚拟线程的调度有延迟(虽然很小)
  3. 已经有性能瓶颈在别处:比如数据库慢,用虚拟线程没用

总结

Java 21的虚拟线程确实是个大杀器,但也不是银弹。我的建议:

  1. 新项目:直接用Spring Boot 3.2 + 虚拟线程(配置spring.threads.virtual.enabled=true
  2. 老项目:先评估瓶颈在哪里,如果是线程池不够用,再考虑迁移
  3. 压测验证:不要盲目迁移,先在测试环境压测,确认有效果再上生产

最后说一句:虚拟线程不是万能的,但它确实能让你的应用在IO密集型场景下性能提升一个数量级


参考资料

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐