1. 环境要求与配置
1.1 依赖配置 (pom.xml)
xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
</parent>
<groupId>com.example</groupId>
<artifactId>virtual-threads-demo</artifactId>
<version>1.0.0</version>
<properties>
<java.version>21</java.version>
</properties>
<dependencies>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Boot Actuator (监控) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- 数据库相关 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<!-- 测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>1.2 application.yml 配置
yaml
server:
port: 8080
spring:
application:
name: virtual-threads-demo
# H2 数据库配置
datasource:
url: jdbc:h2:mem:testdb
driver-class-name: org.h2.Driver
username: sa
password:
jpa:
hibernate:
ddl-auto: create-drop
show-sql: true
properties:
hibernate:
dialect: org.hibernate.dialect.H2Dialect
format_sql: true
# 虚拟线程配置
spring.threads:
virtual:
enabled: true # 启用虚拟线程
# Actuator 端点配置
management:
endpoints:
web:
exposure:
include: health,metrics,threaddump
metrics:
tags:
application: ${spring.application.name}2. 虚拟线程基础配置
2.1 配置虚拟线程 Executor
java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
@Configuration
@EnableAsync
public class VirtualThreadConfig {
/**
* 创建虚拟线程池
* 用于 @Async 注解
*/
@Bean(name = "virtualThreadExecutor")
public Executor virtualThreadExecutor() {
return Executors.newVirtualThreadPerTaskExecutor();
}
/**
* 创建用于 CPU 密集型任务的虚拟线程池
* 限制并发数,避免过多线程竞争 CPU
*/
@Bean(name = "cpuIntensiveExecutor")
public Executor cpuIntensiveExecutor() {
int availableProcessors = Runtime.getRuntime().availableProcessors();
return Executors.newThreadPerTaskExecutor(
Thread.ofVirtual()
.name("cpu-intensive-", 0)
.factory()
);
}
}2.2 虚拟线程监控器
java
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadMXBean;
@Component
public class VirtualThreadHealthIndicator implements HealthIndicator {
@Override
public Health health() {
ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
long totalThreads = threadBean.getThreadCount();
long daemonThreads = threadBean.getDaemonThreadCount();
// 统计虚拟线程(需要 JDK 21+)
long virtualThreads = Thread.getAllStackTraces()
.keySet()
.stream()
.filter(Thread::isVirtual)
.count();
return Health.up()
.withDetail("total_threads", totalThreads)
.withDetail("daemon_threads", daemonThreads)
.withDetail("virtual_threads", virtualThreads)
.withDetail("platform_threads", totalThreads - virtualThreads)
.build();
}
}3. 虚拟线程在 Web 层的应用
3.1 REST Controller 使用虚拟线程
java
import org.springframework.web.bind.annotation.*;
import org.springframework.http.ResponseEntity;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadLocalRandom;
@Slf4j
@RestController
@RequestMapping("/api/virtual-threads")
public class VirtualThreadController {
private final ExecutorService virtualThreadExecutor =
Executors.newVirtualThreadPerTaskExecutor();
/**
* 示例1: 简单的虚拟线程异步处理
*/
@GetMapping("/hello")
public CompletableFuture<String> helloAsync() {
return CompletableFuture.supplyAsync(() -> {
try {
// 模拟IO操作
Thread.sleep(ThreadLocalRandom.current().nextInt(100, 500));
return "Hello from virtual thread: " + Thread.currentThread();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException(e);
}
}, virtualThreadExecutor);
}
/**
* 示例2: 并行处理多个IO操作
*/
@GetMapping("/parallel-io")
public CompletableFuture<ResponseEntity<?>> parallelIOOperations() {
var future1 = CompletableFuture.supplyAsync(() -> {
simulateIO("Database Query", 200);
return "DB Result";
}, virtualThreadExecutor);
var future2 = CompletableFuture.supplyAsync(() -> {
simulateIO("External API Call", 300);
return "API Result";
}, virtualThreadExecutor);
var future3 = CompletableFuture.supplyAsync(() -> {
simulateIO("File System Operation", 150);
return "File Result";
}, virtualThreadExecutor);
return CompletableFuture.allOf(future1, future2, future3)
.thenApply(v -> ResponseEntity.ok()
.body(String.format("All operations completed: %s, %s, %s",
future1.join(), future2.join(), future3.join())));
}
/**
* 示例3: 虚拟线程处理 WebClient 调用
*/
@GetMapping("/webclient-example")
public CompletableFuture<ResponseEntity<?>> webClientExample() {
return CompletableFuture.supplyAsync(() -> {
// 在实际应用中,这里会使用 WebClient 进行HTTP调用
simulateIO("HTTP Request to external service", 400);
return ResponseEntity.ok()
.body(Map.of(
"status", "success",
"thread", Thread.currentThread().toString(),
"message", "WebClient call completed"
));
}, virtualThreadExecutor);
}
private void simulateIO(String operation, int delayMillis) {
log.info("{} starting on thread: {}", operation, Thread.currentThread());
try {
Thread.sleep(delayMillis);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException(e);
}
log.info("{} completed on thread: {}", operation, Thread.currentThread());
}
}4. Service 层使用虚拟线程
4.1 异步 Service 示例
java
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import lombok.extern.slf4j.Slf4j;
import lombok.RequiredArgsConstructor;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.Map;
@Slf4j
@Service
@RequiredArgsConstructor
public class VirtualThreadService {
private final Map<Long, String> taskStore = new ConcurrentHashMap<>();
/**
* 使用 @Async 注解配合虚拟线程执行器
*/
@Async("virtualThreadExecutor")
public CompletableFuture<String> processTask(Long taskId) {
log.info("Processing task {} on virtual thread: {}",
taskId, Thread.currentThread());
// 模拟耗时IO操作
simulateIOOperation(300);
String result = "Task-" + taskId + "-completed";
taskStore.put(taskId, result);
return CompletableFuture.completedFuture(result);
}
/**
* 批量处理任务 - 每个任务在独立的虚拟线程中执行
*/
public CompletableFuture<List<String>> processBatch(List<Long> taskIds) {
List<CompletableFuture<String>> futures = taskIds.stream()
.map(taskId -> CompletableFuture.supplyAsync(() -> {
simulateIOOperation(200);
String result = "Batch-Task-" + taskId;
taskStore.put(taskId, result);
return result;
}, Executors.newVirtualThreadPerTaskExecutor()))
.toList();
return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
.thenApply(v -> futures.stream()
.map(CompletableFuture::join)
.toList());
}
/**
* 模拟数据库操作的虚拟线程使用
*/
@Async("virtualThreadExecutor")
public CompletableFuture<List<User>> findUsersByName(String name) {
return CompletableFuture.supplyAsync(() -> {
// 在实际应用中,这里会调用 Repository
simulateIOOperation("Database query for users: " + name, 250);
return List.of(
new User(1L, "张三", "zhangsan@example.com"),
new User(2L, "李四", "lisi@example.com")
);
});
}
private void simulateIOOperation(int delayMillis) {
try {
Thread.sleep(delayMillis);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
private void simulateIOOperation(String operation, int delayMillis) {
log.info("IO Operation '{}' on thread: {}",
operation, Thread.currentThread());
simulateIOOperation(delayMillis);
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class User {
private Long id;
private String name;
private String email;
}
}5. 数据库操作与虚拟线程
5.1 Repository 层
java
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
/**
* 异步查询方法
*/
@Async("virtualThreadExecutor")
@Query("SELECT u FROM User u WHERE u.name LIKE %:name%")
CompletableFuture<List<User>> findUsersByNameAsync(String name);
/**
* 异步保存方法
*/
@Async("virtualThreadExecutor")
default CompletableFuture<User> saveUserAsync(User user) {
return CompletableFuture.completedFuture(save(user));
}
}
@Entity
@Table(name = "users")
@Data
@NoArgsConstructor
@AllArgsConstructor
class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String email;
private LocalDateTime createdAt;
@PrePersist
protected void onCreate() {
createdAt = LocalDateTime.now();
}
}5.2 事务处理示例
java
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.CompletableFuture;
@Slf4j
@Service
@RequiredArgsConstructor
public class TransactionalVirtualThreadService {
private final UserRepository userRepository;
private final ExecutorService virtualThreadExecutor =
Executors.newVirtualThreadPerTaskExecutor();
/**
* 在虚拟线程中执行事务操作
* 注意:事务边界管理需要小心
*/
public CompletableFuture<User> createUserInTransaction(String name, String email) {
return CompletableFuture.supplyAsync(() -> {
// 在虚拟线程中执行数据库操作
User user = new User();
user.setName(name);
user.setEmail(email);
log.info("Saving user on virtual thread: {}", Thread.currentThread());
return userRepository.save(user);
}, virtualThreadExecutor);
}
/**
* 批量插入使用虚拟线程
*/
public CompletableFuture<List<User>> batchCreateUsers(List<User> users) {
List<CompletableFuture<User>> futures = users.stream()
.map(user -> CompletableFuture.supplyAsync(() -> {
try {
// 每个用户插入都在自己的虚拟线程中执行
return userRepository.save(user);
} catch (Exception e) {
log.error("Error saving user: {}", user.getName(), e);
throw new RuntimeException(e);
}
}, virtualThreadExecutor))
.toList();
return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
.thenApply(v -> futures.stream()
.map(CompletableFuture::join)
.toList());
}
}6. 性能测试与监控
6.1 测试 Controller
java
import org.springframework.web.bind.annotation.*;
import org.springframework.http.ResponseEntity;
import lombok.extern.slf4j.Slf4j;
import java.time.Duration;
import java.time.Instant;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.IntStream;
@Slf4j
@RestController
@RequestMapping("/api/performance")
public class PerformanceTestController {
private final ExecutorService virtualThreadExecutor =
Executors.newVirtualThreadPerTaskExecutor();
private final ExecutorService platformThreadExecutor =
Executors.newFixedThreadPool(100);
private final AtomicInteger virtualThreadCounter = new AtomicInteger(0);
private final AtomicInteger platformThreadCounter = new AtomicInteger(0);
/**
* 虚拟线程性能测试
*/
@PostMapping("/test/virtual-threads")
public ResponseEntity<TestResult> testVirtualThreads(@RequestParam int requestCount) {
log.info("Starting virtual threads test with {} requests", requestCount);
Instant start = Instant.now();
// 使用虚拟线程处理并发请求
List<CompletableFuture<Void>> futures = IntStream.range(0, requestCount)
.mapToObj(i -> CompletableFuture.runAsync(() -> {
virtualThreadCounter.incrementAndGet();
simulateIOOperation(100); // 模拟100ms的IO操作
}, virtualThreadExecutor))
.toList();
// 等待所有任务完成
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
Duration duration = Duration.between(start, Instant.now());
TestResult result = new TestResult(
"virtual-threads",
requestCount,
virtualThreadCounter.get(),
duration.toMillis(),
duration.toMillis() / (double) requestCount
);
log.info("Virtual threads test completed: {}", result);
return ResponseEntity.ok(result);
}
/**
* 平台线程性能测试(对比)
*/
@PostMapping("/test/platform-threads")
public ResponseEntity<TestResult> testPlatformThreads(@RequestParam int requestCount) {
log.info("Starting platform threads test with {} requests", requestCount);
Instant start = Instant.now();
List<CompletableFuture<Void>> futures = IntStream.range(0, requestCount)
.mapToObj(i -> CompletableFuture.runAsync(() -> {
platformThreadCounter.incrementAndGet();
simulateIOOperation(100);
}, platformThreadExecutor))
.toList();
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
Duration duration = Duration.between(start, Instant.now());
TestResult result = new TestResult(
"platform-threads",
requestCount,
platformThreadCounter.get(),
duration.toMillis(),
duration.toMillis() / (double) requestCount
);
log.info("Platform threads test completed: {}", result);
return ResponseEntity.ok(result);
}
/**
* 混合负载测试
*/
@PostMapping("/test/mixed-load")
public ResponseEntity<Map<String, Object>> testMixedLoad() {
int ioBoundTasks = 1000;
int cpuBoundTasks = 100;
Instant start = Instant.now();
// IO密集型任务使用虚拟线程
List<CompletableFuture<Void>> ioTasks = IntStream.range(0, ioBoundTasks)
.mapToObj(i -> CompletableFuture.runAsync(() -> {
simulateIOOperation(50);
}, virtualThreadExecutor))
.toList();
// CPU密集型任务使用平台线程池
List<CompletableFuture<Void>> cpuTasks = IntStream.range(0, cpuBoundTasks)
.mapToObj(i -> CompletableFuture.runAsync(() -> {
performCpuIntensiveWork();
}, platformThreadExecutor))
.toList();
CompletableFuture.allOf(
Stream.concat(ioTasks.stream(), cpuTasks.stream())
.toArray(CompletableFuture[]::new)
).join();
Duration duration = Duration.between(start, Instant.now());
Map<String, Object> result = Map.of(
"total_tasks", ioBoundTasks + cpuBoundTasks,
"io_tasks", ioBoundTasks,
"cpu_tasks", cpuBoundTasks,
"total_time_ms", duration.toMillis(),
"throughput_tasks_per_second",
(ioBoundTasks + cpuBoundTasks) / (duration.toMillis() / 1000.0)
);
return ResponseEntity.ok(result);
}
private void simulateIOOperation(int delayMillis) {
try {
Thread.sleep(delayMillis);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
private void performCpuIntensiveWork() {
// 模拟CPU密集型计算
long sum = 0;
for (int i = 0; i < 1000000; i++) {
sum += i * i;
}
}
@Data
@AllArgsConstructor
public static class TestResult {
private String testType;
private int totalRequests;
private int processedRequests;
private long totalTimeMs;
private double averageTimePerRequestMs;
}
}6.2 监控端点
java
import org.springframework.boot.actuate.endpoint.annotation.*;
import org.springframework.stereotype.Component;
import java.lang.management.*;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Component
@Endpoint(id = "virtualthreads")
public class VirtualThreadsEndpoint {
private final Map<String, Object> metrics = new ConcurrentHashMap<>();
@ReadOperation
public Map<String, Object> getVirtualThreadMetrics() {
ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
// 获取所有线程
long[] threadIds = threadBean.getAllThreadIds();
int virtualThreadCount = 0;
int platformThreadCount = 0;
for (long threadId : threadIds) {
ThreadInfo threadInfo = threadBean.getThreadInfo(threadId);
if (threadInfo != null) {
// 检查是否为虚拟线程(通过线程名或其它特征)
if (threadInfo.getThreadName().contains("VirtualThread")) {
virtualThreadCount++;
} else {
platformThreadCount++;
}
}
}
metrics.put("virtual_thread_count", virtualThreadCount);
metrics.put("platform_thread_count", platformThreadCount);
metrics.put("total_thread_count", threadBean.getThreadCount());
metrics.put("peak_thread_count", threadBean.getPeakThreadCount());
metrics.put("daemon_thread_count", threadBean.getDaemonThreadCount());
// 内存信息
Runtime runtime = Runtime.getRuntime();
metrics.put("max_memory_mb", runtime.maxMemory() / 1024 / 1024);
metrics.put("total_memory_mb", runtime.totalMemory() / 1024 / 1024);
metrics.put("free_memory_mb", runtime.freeMemory() / 1024 / 1024);
metrics.put("used_memory_mb",
(runtime.totalMemory() - runtime.freeMemory()) / 1024 / 1024);
return metrics;
}
}7. 高级特性与最佳实践
7.1 虚拟线程池配置器
java
import org.springframework.boot.web.embedded.tomcat.TomcatProtocolHandlerCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.*;
@Configuration
public class AdvancedVirtualThreadConfig {
/**
* 配置 Tomcat 使用虚拟线程处理请求
*/
@Bean
public TomcatProtocolHandlerCustomizer<?> protocolHandlerVirtualThreadExecutorCustomizer() {
return protocolHandler -> {
protocolHandler.setExecutor(Executors.newVirtualThreadPerTaskExecutor());
};
}
/**
* 自定义虚拟线程工厂,添加监控和命名
*/
@Bean
public ThreadFactory monitoredVirtualThreadFactory() {
return Thread.ofVirtual()
.name("app-virtual-", 0)
.uncaughtExceptionHandler((t, e) -> {
// 自定义异常处理
System.err.println("Uncaught exception in virtual thread: " + t.getName());
e.printStackTrace();
})
.factory();
}
/**
* 用于定时任务的虚拟线程调度器
*/
@Bean
public ScheduledExecutorService virtualThreadScheduler() {
return Executors.newScheduledThreadPool(
0, // 核心线程数设为0,全部使用虚拟线程
Thread.ofVirtual().name("scheduled-", 0).factory()
);
}
}7.2 虚拟线程异常处理
java
import org.springframework.web.bind.annotation.*;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.*;
@Slf4j
@RestControllerAdvice
public class VirtualThreadExceptionHandler {
/**
* 处理虚拟线程中的异常
*/
@ExceptionHandler(CompletionException.class)
public ResponseEntity<ErrorResponse> handleCompletionException(
CompletionException ex) {
Throwable cause = ex.getCause();
log.error("Virtual thread task failed: {}", cause.getMessage(), cause);
return ResponseEntity.status(500)
.body(new ErrorResponse("VIRTUAL_THREAD_ERROR",
"Task execution failed: " + cause.getMessage()));
}
/**
* 处理线程中断异常
*/
@ExceptionHandler(InterruptedException.class)
public ResponseEntity<ErrorResponse> handleInterruptedException(
InterruptedException ex) {
log.warn("Virtual thread was interrupted");
Thread.currentThread().interrupt(); // 重新设置中断状态
return ResponseEntity.status(503)
.body(new ErrorResponse("THREAD_INTERRUPTED",
"Request processing was interrupted"));
}
@Data
@AllArgsConstructor
public static class ErrorResponse {
private String code;
private String message;
}
}8. 测试类
java
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.web.client.TestRestTemplate;
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class VirtualThreadApplicationTests {
@Autowired
private TestRestTemplate restTemplate;
@Test
void testVirtualThreadHelloEndpoint() {
var response = restTemplate.getForObject(
"/api/virtual-threads/hello",
String.class
);
assertThat(response).contains("Hello from virtual thread");
}
@Test
void testVirtualThreadPerformance() throws Exception {
ExecutorService testExecutor = Executors.newVirtualThreadPerTaskExecutor();
long startTime = System.currentTimeMillis();
// 并发执行100个请求
var futures = java.util.stream.IntStream.range(0, 100)
.mapToObj(i -> CompletableFuture.runAsync(() -> {
String response = restTemplate.getForObject(
"/api/virtual-threads/hello",
String.class
);
assertThat(response).isNotNull();
}, testExecutor))
.toArray(CompletableFuture[]::new);
CompletableFuture.allOf(futures).get(10, TimeUnit.SECONDS);
long duration = System.currentTimeMillis() - startTime;
System.out.printf("100 requests completed in %d ms%n", duration);
testExecutor.shutdown();
}
}9. 主启动类
java
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
@SpringBootApplication
@EnableAsync
public class VirtualThreadApplication {
public static void main(String[] args) {
SpringApplication.run(VirtualThreadApplication.class, args);
// 打印虚拟线程相关信息
System.out.println("=".repeat(50));
System.out.println("Virtual Thread Demo Application Started");
System.out.println("Java Version: " + System.getProperty("java.version"));
System.out.println("Virtual Threads Enabled: true");
System.out.println("=".repeat(50));
}
}10. 最佳实践总结
10.1 使用场景
IO密集型任务:数据库查询、HTTP请求、文件操作等
高并发请求处理:Web服务器处理大量并发连接
异步任务处理:后台任务、批处理作业
微服务通信:服务间调用、消息处理
10.2 注意事项
避免 CPU 密集型任务:虚拟线程不适合长时间CPU计算
线程局部变量:使用
ThreadLocal要小心,考虑使用ScopedValue(JDK 21+)资源管理:虚拟线程虽然轻量,但大量创建仍需注意资源使用
调试监控:虚拟线程的堆栈跟踪与传统线程不同
10.3 配置建议
Web 服务器:为 Tomcat/Undertow 配置虚拟线程执行器
数据库连接池:适当增加连接数以匹配虚拟线程数量
外部调用:配置合适的超时和重试策略
监控:使用 Actuator 监控虚拟线程使用情况
这个完整的示例展示了在 Spring Boot 4.0.x + JDK 21 中如何使用虚拟线程,涵盖了从基础配置到高级用法的各个方面。