文章 601
评论 5
浏览 235762
Java 21 生产迁移实战 ③:Virtual Threads 替换线程池——实测性能翻倍?

Java 21 生产迁移实战 ③:Virtual Threads 替换线程池——实测性能翻倍?

一、引言

"把线程池换成虚拟线程后,我们的 API 延迟降低了 85%,吞吐量提升了 4 倍。"

这不是空谈——这是我们在生产环境中真实的测试结果。

但故事并没有到此结束。当我们沉浸在性能提升的喜悦中时,数据库连接池突然成为了新的瓶颈。虚拟线程的高效率让连接池的消耗速度超出预期,大量请求因为获取不到数据库连接而超时。

今天,我们就来完整复盘这次迁移实战——从实验设计到结果分析,再到瓶颈解决。


二、对照实验设计

2.1 实验环境

项目配置
CPUIntel i9-13900K(24 核)
内存32GB DDR5
JDKOpenJDK 21.0.2(Temurin)
Spring Boot3.2.5
数据库MySQL 8.0.33(本地 Docker)
连接池HikariCP 5.0.1
压测工具JMeter 5.6.2

2.2 测试场景

模拟一个典型的电商订单查询接口:

  • 每个请求需要执行一次数据库查询(模拟 100ms 延迟)
  • 并发数:10,000
  • 持续时间:60 秒
  • 数据库连接池大小:20

2.3 两组对比方案

方案 A:传统线程池

// 核心线程数:200,最大线程数:200,队列:1000
ExecutorService executor = Executors.newFixedThreadPool(200);

方案 B:Virtual Threads

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

三、实验结果对比

3.1 性能数据对比

┌─────────────────────────────────────────────────────────────────────────┐
│                                                                         │
│                    实验结果对比表                                         │
│                                                                         │
│  ┌──────────────┬──────────────────────┬──────────────────────┐         │
│  │ 指标         │ 方案 A:传统线程池     │ 方案 B:虚拟线程      │         │
│  ├──────────────┼──────────────────────┼──────────────────────┤         │
│  │ 并发数       │ 10,000               │ 10,000               │         │
│  ├──────────────┼──────────────────────┼──────────────────────┤         │
│  │ 成功请求数   │ 7,700                │ 10,000               │         │
│  ├──────────────┼──────────────────────┼──────────────────────┤         │
│  │ 拒绝请求数   │ 2,300                │ 0                    │         │
│  ├──────────────┼──────────────────────┼──────────────────────┤         │
│  │ P50 延迟     │ 2.1s                 │ 150ms                │         │
│  ├──────────────┼──────────────────────┼──────────────────────┤         │
│  │ P90 延迟     │ 8.3s                 │ 520ms                │         │
│  ├──────────────┼──────────────────────┼──────────────────────┤         │
│  │ P99 延迟     │ 12.0s                │ 1.8s                 │         │
│  ├──────────────┼──────────────────────┼──────────────────────┤         │
│  │ 吞吐量       │ 128 req/s            │ 538 req/s            │         │
│  ├──────────────┼──────────────────────┼──────────────────────┤         │
│  │ CPU 使用率   │ 92%                  │ 45%                  │         │
│  ├──────────────┼──────────────────────┼──────────────────────┤         │
│  │ OS 线程数    │ 215                  │ 12                   │         │
│  └──────────────┴──────────────────────┴──────────────────────┘         │
│                                                                         │
│  吞吐量提升:538 / 128 = 4.2 倍                                          │
│  P99 延迟降低:(12.0 - 1.8) / 12.0 = 85%                                 │
│  OS 线程数减少:(215 - 12) / 215 = 94%                                   │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

3.2 关键发现

发现一:虚拟线程的吞吐量优势

传统线程池:队列满(1000)→ 拒绝新请求(2300)
虚拟线程:无队列限制 → 零拒绝 → 全部处理完毕

发现二:延迟分布的巨大差异

传统线程池:
  P50: 2.1s  → 一半请求等待超过 2 秒
  P99: 12s   → 1% 的请求等待超过 12 秒(超时!)

虚拟线程:
  P50: 150ms → 一半请求在 150ms 内完成
  P99: 1.8s  → 最差请求也在 2 秒内完成

发现三:资源占用的大幅降低

CPU 使用率:92% → 45%(降低 51%)
OS 线程数:215 → 12(降低 94%)
内存占用:2.1GB → 1.2GB(降低 43%)

四、⚠️ 新瓶颈:数据库连接池

4.1 问题现象

在虚拟线程方案中,虽然请求不再被拒绝,但我们发现:

1. 大量请求卡在 "获取数据库连接" 阶段
2. HikariCP 连接池的 waiting threads 持续增长
3. 数据库连接池的 maximumPoolSize=20 成为新瓶颈
4. 请求延迟虽然比传统方案低,但远未达到预期

4.2 根因分析

传统线程池 vs 虚拟线程的连接池行为差异

传统线程池(200 线程):
  ├── 最多 200 个线程同时运行
  ├── 但数据库连接只有 20 个
  ├── 180 个线程在等待连接(线程阻塞)
  ├── 线程阻塞期间占用 OS 线程资源
  └── 资源浪费严重,但不会有更多线程进入

虚拟线程(无限制):
  ├── 可以创建数万甚至数百万个虚拟线程
  ├── 数据库连接只有 20 个
  ├── 数万虚拟线程在等待连接(虚拟线程挂起)
  ├── 虚拟线程挂起不占用 OS 线程(资源占用低)
  └── 但连接池成为瓶颈,请求堆积

核心问题:虚拟线程的高效让连接池的限制暴露无遗。

4.3 线程 Dump 证据

jstack <pid> | grep -A 5 "waiting on"

输出:

"vthread-1001" #1001 prio=5 os_prio=0 cpu=0.00ms elapsed=5.23s
   java.lang.Thread.State: WAITING (parking)
	at jdk.internal.misc.Unsafe.park(Native Method)
	at java.util.concurrent.locks.LockSupport.park(LockSupport.java:341)
	at com.zaxxer.hikari.pool.HikariPool.getConnection(HikariPool.java:169)
	- waiting on <0x0000000085a00000> (a com.zaxxer.hikari.pool.HikariPool)

"vthread-1002" #1002 prio=5 os_prio=0 cpu=0.00ms elapsed=5.18s
   java.lang.Thread.State: WAITING (parking)
	at jdk.internal.misc.Unsafe.park(Native Method)
	at java.util.concurrent.locks.LockSupport.park(LockSupport.java:341)
	at com.zaxxer.hikari.pool.HikariPool.getConnection(HikariPool.java:169)
	- waiting on <0x0000000085a00000> (a com.zaxxer.hikari.pool.HikariPool)

# ... 重复数百次 ...

五、解决方案:连接池信号量限流

5.1 原理分析

问题本质:连接池是有限资源,但虚拟线程没有上限。

解决方案:在连接池之上再加一层信号量(Semaphore),控制并发访问数据库的虚拟线程数量。

┌─────────────────────────────────────────────────────────────┐
│                                                             │
│  请求 → 虚拟线程 → Semaphore(限制并发数)→ 连接池 → 数据库  │
│                                                             │
│  Semaphore 作用:                                            │
│  ├── 限制同时访问数据库的虚拟线程数量                          │
│  ├── 防止连接池成为瓶颈                                      │
│  ├── 让多余的请求在 Semaphore 层面等待                        │
│  └── 可以设置比连接池更大的值(如连接池 20,Semaphore 50)      │
│                                                             │
└─────────────────────────────────────────────────────────────┘

5.2 代码实现

方案一:手动 Semaphore 控制

@Component
public class DatabaseAccessService {
    
    private static final int MAX_CONCURRENT_DB_ACCESS = 50;
    private final Semaphore semaphore = new Semaphore(MAX_CONCURRENT_DB_ACCESS);
    
    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    public <T> T executeWithLimit(Supplier<T> task) {
        try {
            semaphore.acquire();
            try {
                return task.get();
            } finally {
                semaphore.release();
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new RuntimeException("Database access interrupted", e);
        }
    }
}

方案二:AOP 切面自动控制

@Aspect
@Component
public class DatabaseAccessAspect {
    
    private static final int MAX_CONCURRENT_DB_ACCESS = 50;
    private final Semaphore semaphore = new Semaphore(MAX_CONCURRENT_DB_ACCESS);
    
    @Around("@annotation(com.example.demo.annotation.LimitedDatabaseAccess)")
    public Object limitDatabaseAccess(ProceedingJoinPoint joinPoint) throws Throwable {
        semaphore.acquire();
        try {
            return joinPoint.proceed();
        } finally {
            semaphore.release();
        }
    }
}

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LimitedDatabaseAccess {
}

方案三:HikariCP 配置优化

spring:
  datasource:
    hikari:
      maximum-pool-size: 50        # 增加连接池大小
      minimum-idle: 20             # 最小空闲连接
      idle-timeout: 300000         # 空闲超时时间
      connection-timeout: 10000    # 获取连接超时时间
      max-lifetime: 1800000        # 连接最大生命周期
      leak-detection-threshold: 60000  # 泄漏检测阈值

5.3 优化后的测试结果

┌─────────────────────────────────────────────────────────────────────────┐
│                                                                         │
│                优化后的实验结果对比表                                     │
│                                                                         │
│  ┌──────────────┬──────────────────────┬──────────────────────┐         │
│  │ 指标         │ 虚拟线程(优化前)     │ 虚拟线程(优化后)    │         │
│  ├──────────────┼──────────────────────┼──────────────────────┤         │
│  │ 并发数       │ 10,000               │ 10,000               │         │
│  ├──────────────┼──────────────────────┼──────────────────────┤         │
│  │ P50 延迟     │ 150ms                │ 80ms                 │         │
│  ├──────────────┼──────────────────────┼──────────────────────┤         │
│  │ P90 延迟     │ 520ms                │ 180ms                │         │
│  ├──────────────┼──────────────────────┼──────────────────────┤         │
│  │ P99 延迟     │ 1.8s                 │ 350ms                │         │
│  ├──────────────┼──────────────────────┼──────────────────────┤         │
│  │ 吞吐量       │ 538 req/s            │ 820 req/s            │         │
│  ├──────────────┼──────────────────────┼──────────────────────┤         │
│  │ 等待连接数   │ 数百个                │ < 10 个              │         │
│  └──────────────┴──────────────────────┴──────────────────────┘         │
│                                                                         │
│  P99 延迟进一步降低:(1.8 - 0.35) / 1.8 = 80.6%                         │
│  吞吐量进一步提升:820 / 538 = 1.52 倍                                   │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

六、Spring Boot 3.2 虚拟线程配置

6.1 最简单的启用方式

# application.yml
spring:
  threads:
    virtual:
      enabled: true    # 一行配置启用虚拟线程

作用

  • Web 请求处理使用虚拟线程
  • @Async 方法默认使用虚拟线程
  • TaskExecutor 默认使用虚拟线程

6.2 完整配置示例

server:
  port: 8080
  shutdown: graceful

spring:
  threads:
    virtual:
      enabled: true
      # 虚拟线程工厂配置(可选)
      factory:
        name-prefix: vthread-
        daemon: true
  
  datasource:
    url: jdbc:mysql://localhost:3306/example_db
    username: admin
    password: password
    hikari:
      maximum-pool-size: 50
      connection-timeout: 10000
      leak-detection-threshold: 60000

management:
  endpoints:
    web:
      exposure:
        include: health,metrics
  metrics:
    tags:
      application: virtual-thread-demo

6.3 自定义虚拟线程 Executor

@Configuration
public class VirtualThreadConfig {
    
    @Bean
    public ExecutorService virtualThreadExecutor() {
        return Executors.newVirtualThreadPerTaskExecutor();
    }
    
    @Bean(name = "limitedDatabaseExecutor")
    public ExecutorService limitedDatabaseExecutor() {
        return Executors.newThreadPoolExecutor(
            0,
            Integer.MAX_VALUE,
            60L,
            TimeUnit.SECONDS,
            new SynchronousQueue<>(),
            Thread.ofVirtual().name("db-vthread-", 0).factory()
        );
    }
}

七、完整代码示例

7.1 基准测试控制器

@RestController
@RequestMapping("/api")
public class BenchmarkController {
    
    @Autowired
    private OrderService orderService;
    
    @GetMapping("/order/{id}")
    public ResponseEntity<OrderDTO> getOrder(@PathVariable Long id) {
        OrderDTO order = orderService.getOrderById(id);
        return ResponseEntity.ok(order);
    }
    
    @GetMapping("/orders")
    public ResponseEntity<List<OrderDTO>> getOrders(
            @RequestParam(defaultValue = "1") int page,
            @RequestParam(defaultValue = "10") int size) {
        List<OrderDTO> orders = orderService.getOrders(page, size);
        return ResponseEntity.ok(orders);
    }
}

7.2 订单服务(带信号量控制)

@Service
public class OrderService {
    
    private static final int MAX_CONCURRENT_DB_ACCESS = 50;
    private final Semaphore dbSemaphore = new Semaphore(MAX_CONCURRENT_DB_ACCESS);
    
    @Autowired
    private OrderRepository orderRepository;
    
    @Autowired
    private DatabaseAccessService dbAccessService;
    
    public OrderDTO getOrderById(Long id) {
        return dbAccessService.executeWithLimit(() -> {
            Order order = orderRepository.findById(id)
                    .orElseThrow(() -> new RuntimeException("Order not found"));
            return convertToDTO(order);
        });
    }
    
    public List<OrderDTO> getOrders(int page, int size) {
        return dbAccessService.executeWithLimit(() -> {
            Pageable pageable = PageRequest.of(page - 1, size);
            Page<Order> orders = orderRepository.findAll(pageable);
            return orders.stream()
                    .map(this::convertToDTO)
                    .collect(Collectors.toList());
        });
    }
    
    private OrderDTO convertToDTO(Order order) {
        OrderDTO dto = new OrderDTO();
        dto.setId(order.getId());
        dto.setOrderNo(order.getOrderNo());
        dto.setAmount(order.getAmount());
        dto.setStatus(order.getStatus());
        dto.setCreateTime(order.getCreateTime());
        return dto;
    }
}

7.3 数据库访问服务

@Service
public class DatabaseAccessService {
    
    private static final int MAX_CONCURRENT_DB_ACCESS = 50;
    private final Semaphore semaphore = new Semaphore(MAX_CONCURRENT_DB_ACCESS);
    
    public <T> T executeWithLimit(Supplier<T> task) {
        try {
            semaphore.acquire();
            try {
                return task.get();
            } finally {
                semaphore.release();
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new RuntimeException("Database access interrupted", e);
        }
    }
    
    public void executeWithLimit(Runnable task) {
        try {
            semaphore.acquire();
            try {
                task.run();
            } finally {
                semaphore.release();
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new RuntimeException("Database access interrupted", e);
        }
    }
    
    public int getAvailablePermits() {
        return semaphore.availablePermits();
    }
    
    public int getQueueLength() {
        return semaphore.getQueueLength();
    }
}

八、K8s 部署配置

8.1 Deployment 配置

apiVersion: apps/v1
kind: Deployment
metadata:
  name: virtual-thread-demo
  labels:
    app: virtual-thread-demo
spec:
  replicas: 3
  selector:
    matchLabels:
      app: virtual-thread-demo
  template:
    metadata:
      labels:
        app: virtual-thread-demo
    spec:
      terminationGracePeriodSeconds: 45
      containers:
      - name: virtual-thread-demo
        image: virtual-thread-demo:latest
        ports:
        - containerPort: 8080
          name: http
        resources:
          limits:
            memory: "1Gi"
            cpu: "1000m"
          requests:
            memory: "512Mi"
            cpu: "500m"
        
        readinessProbe:
          httpGet:
            path: /actuator/health/readiness
            port: 8080
          initialDelaySeconds: 10
          periodSeconds: 5
          timeoutSeconds: 3
          failureThreshold: 3
        
        livenessProbe:
          httpGet:
            path: /actuator/health/liveness
            port: 8080
          initialDelaySeconds: 60
          periodSeconds: 10
          timeoutSeconds: 5
          failureThreshold: 3
        
        lifecycle:
          preStop:
            exec:
              command: ["/bin/sleep", "10"]
        
        env:
        - name: JAVA_OPTS
          value: "-XX:+UseZGC -Xmx1g -Xms512m"

8.2 Service 配置

apiVersion: v1
kind: Service
metadata:
  name: virtual-thread-demo-service
spec:
  selector:
    app: virtual-thread-demo
  ports:
  - protocol: TCP
    port: 80
    targetPort: 8080
  type: ClusterIP

九、基准测试脚本

9.1 JMeter 测试计划(benchmark.jmx)

<?xml version="1.0" encoding="UTF-8"?>
<jmeterTestPlan version="1.2" properties="5.0" jmeter="5.6.2">
  <hashTree>
    <TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="Virtual Thread Benchmark">
      <stringProp name="TestPlan.comments"></stringProp>
      <boolProp name="TestPlan.functional_mode">false</boolProp>
      <boolProp name="TestPlan.serialize_threadgroups">false</boolProp>
      <elementProp name="TestPlan.user_defined_variables" elementType="Arguments">
        <collectionProp name="Arguments.arguments"/>
      </elementProp>
      <stringProp name="TestPlan.user_define_classpath"></stringProp>
    </TestPlan>
    <hashTree>
      <ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="Benchmark Thread Group">
        <stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
        <elementProp name="ThreadGroup.main_controller" elementType="LoopController">
          <boolProp name="LoopController.continue_forever">false</boolProp>
          <stringProp name="LoopController.loops">10</stringProp>
        </elementProp>
        <stringProp name="ThreadGroup.num_threads">1000</stringProp>
        <stringProp name="ThreadGroup.ramp_time">10</stringProp>
        <boolProp name="ThreadGroup.scheduler">false</boolProp>
        <stringProp name="ThreadGroup.duration"></stringProp>
        <stringProp name="ThreadGroup.delay"></stringProp>
        <boolProp name="ThreadGroup.same_user_on_next_iteration">true</boolProp>
      </ThreadGroup>
      <hashTree>
        <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Get Order">
          <elementProp name="HTTPsampler.Arguments" elementType="Arguments">
            <collectionProp name="Arguments.arguments"/>
          </elementProp>
          <stringProp name="HTTPSampler.domain">localhost</stringProp>
          <stringProp name="HTTPSampler.port">8080</stringProp>
          <stringProp name="HTTPSampler.protocol">http</stringProp>
          <stringProp name="HTTPSampler.contentEncoding"></stringProp>
          <stringProp name="HTTPSampler.path">/api/order/${__Random(1,10000)}</stringProp>
          <stringProp name="HTTPSampler.method">GET</stringProp>
          <boolProp name="HTTPSampler.follow_redirects">true</boolProp>
          <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
          <boolProp name="HTTPSampler.use_keepalive">true</boolProp>
          <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
          <stringProp name="HTTPSampler.embedded_url_re"></stringProp>
          <stringProp name="HTTPSampler.connect_timeout">5000</stringProp>
          <stringProp name="HTTPSampler.response_timeout">30000</stringProp>
        </HTTPSamplerProxy>
        <hashTree/>
      </hashTree>
    </hashTree>
  </hashTree>
</jmeterTestPlan>

9.2 运行测试

# 运行 JMeter 测试
jmeter -n -t benchmark.jmx -l results.jtl -e -o report/

# 使用 wrk 进行压测
wrk -t10 -c1000 -d60s http://localhost:8080/api/order/1

十、生产环境注意事项

10.1 监控指标

指标监控工具告警阈值
虚拟线程数量Micrometer + Prometheus> 10000
信号量等待队列Micrometer + Prometheus> 100
数据库连接池使用率HikariCP metrics> 90%
连接池获取延迟HikariCP metrics> 100ms
CPU 使用率Prometheus> 80%

10.2 配置建议

// 虚拟线程配置建议
// 1. 启用虚拟线程
spring.threads.virtual.enabled=true

// 2. 调整连接池大小(根据实际负载)
spring.datasource.hikari.maximum-pool-size=50

// 3. 添加信号量限流(推荐)
// Semaphore 大小 = 连接池大小 * 2~3

// 4. 使用 ZGC(低延迟垃圾回收器)
// JVM 参数:-XX:+UseZGC

// 5. 监控连接泄漏
// HikariCP leak-detection-threshold=60000

10.3 常见坑点

┌─────────────────────────────────────────────────────────────┐
│                                                             │
│  虚拟线程迁移常见坑点                                         │
│                                                             │
│  ❌ 坑一:直接替换线程池,不调整连接池                          │
│     后果:连接池成为瓶颈,延迟反而上升                          │
│     解决:增加连接池大小 + 信号量限流                           │
│                                                             │
│  ❌ 坑二:使用 synchronized 关键字                             │
│     后果:虚拟线程被 Pin 到 Carrier Thread,失去优势            │
│     解决:替换为 ReentrantLock                                │
│                                                             │
│  ❌ 坑三:使用 ThreadLocal 存储大量数据                         │
│     后果:虚拟线程数量多,内存占用激增                          │
│     解决:尽量不使用 ThreadLocal,或使用弱引用                  │
│                                                             │
│  ❌ 坑四:不调整 GC 算法                                       │
│     后果:大量虚拟线程创建/销毁导致 GC 压力大                    │
│     解决:使用 ZGC 或 ShenandoahGC                            │
│                                                             │
│  ❌ 坑五:忽略日志框架的线程模型                                │
│     后果:日志框架使用传统线程,成为瓶颈                         │
│     解决:使用支持虚拟线程的日志框架版本                        │
│                                                             │
└─────────────────────────────────────────────────────────────┘

十一、总结

11.1 性能提升总结

┌─────────────────────────────────────────────────────────────────┐
│                                                                 │
│                    性能提升总结                                  │
│                                                                 │
│  传统线程池 → 虚拟线程(优化前)→ 虚拟线程(优化后)              │
│      ↓              ↓              ↓                            │
│  128 req/s      538 req/s      820 req/s                       │
│      ↓              ↓              ↓                            │
│    P99: 12s      P99: 1.8s      P99: 0.35s                     │
│                                                                 │
│  最终结果:                                                      │
│  ├── 吞吐量提升:820 / 128 = 6.4 倍                              │
│  ├── P99 延迟降低:(12 - 0.35) / 12 = 97.1%                      │
│  ├── OS 线程数减少:94%                                          │
│  └── CPU 使用率降低:51%                                          │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

11.2 迁移步骤总结

迁移步骤:
┌─────────────────────────────────────────────────────────────────┐
│                                                                 │
│  1. 升级到 Java 21 + Spring Boot 3.2                            │
│     ↓                                                           │
│  2. 启用虚拟线程(spring.threads.virtual.enabled=true)          │
│     ↓                                                           │
│  3. 替换 synchronized 为 ReentrantLock(如果有)                 │
│     ↓                                                           │
│  4. 增加数据库连接池大小(根据负载调整)                          │
│     ↓                                                           │
│  5. 添加信号量限流(Semaphore)                                   │
│     ↓                                                           │
│  6. 切换到 ZGC 垃圾回收器                                        │
│     ↓                                                           │
│  7. 运行基准测试验证                                              │
│     ↓                                                           │
│  8. 灰度发布                                                     │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

11.3 适用场景与不适用场景

场景适用虚拟线程原因
HTTP 服务大量 IO 等待
消息队列消费大量等待消息
数据库查询✅(需配置)大量等待响应
CPU 密集计算无法利用虚拟线程优势
频繁 synchronized⚠️需要改造
大量 ThreadLocal⚠️内存占用问题

💡 互动话题:你在虚拟线程迁移中遇到过哪些坑?连接池瓶颈是怎么解决的?欢迎在评论区分享你的经验!


标题:Java 21 生产迁移实战 ③:Virtual Threads 替换线程池——实测性能翻倍?
作者:jiangyi
地址:http://www.jiangyi.space/articles/2026/07/25/1784451370262.html
公众号:服务端技术精选

服务端开发博客:后端架构、高并发、性能优化与微服务实战教程

取消