一、概述
Java 21引入了虚拟线程(Virtual Threads),这是Project Loom的核心特性。虚拟线程是轻量级线程,可以显著提高应用程序的并发处理能力,特别适合I/O密集型任务。
在Spring Boot 3.2+中,已经内置了对虚拟线程的支持,可以通过简单的配置启用。
二、Spring Boot 3.2+ 虚拟线程自动配置
1. 启用虚拟线程(已在项目中配置)
在 application.yml 中已经配置:
yaml
spring:
threads:
virtual:
enabled: true
这个配置会自动:
- 为
@Async方法启用虚拟线程执行器 - 为 Spring MVC 的请求处理启用虚拟线程
- 为 Spring WebFlux 启用虚拟线程
2. 验证虚拟线程是否启用
可以通过以下方式验证:
java
@SpringBootTest
public class VirtualThreadTest {
@Test
public void testVirtualThread() {
Thread thread = Thread.ofVirtual().start(() -> {
System.out.println("虚拟线程名称: " + Thread.currentThread().getName());
System.out.println("是否为虚拟线程: " + Thread.currentThread().isVirtual());
});
try {
thread.join();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
三、使用方式
方式一:Spring Boot自动配置(推荐)
1.1 异步方法中使用虚拟线程
配置类改造(推荐):
java
/**
* 异步线程池配置(虚拟线程版本)
* 当 spring.threads.virtual.enabled=true 时使用虚拟线程
*
* @author
* @date 2024-10-31
*/
@Slf4j
@Configuration
@EnableAsync
public class VirtualThreadAsyncConfig implements AsyncConfigurer {
/**
* 虚拟线程异步执行器
* Spring Boot 3.2+ 会自动创建虚拟线程执行器,这里提供手动配置示例
*/
@Bean(name = "virtualThreadExecutor")
@ConditionalOnProperty(name = "spring.threads.virtual.enabled", havingValue = "true", matchIfMissing = false)
public Executor virtualThreadExecutor() {
return new TaskExecutorAdapter(Executors.newVirtualThreadPerTaskExecutor());
}
/**
* 默认异步执行器
* 如果启用虚拟线程,Spring Boot会自动使用虚拟线程执行器
*/
@Override
public Executor getAsyncExecutor() {
// Spring Boot 3.2+ 会自动使用虚拟线程执行器(如果启用)
// 如果需要手动指定,可以返回 virtualThreadExecutor()
return Executors.newVirtualThreadPerTaskExecutor();
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new SimpleAsyncUncaughtExceptionHandler();
}
}
使用示例:
java
@Service
@Slf4j
public class ExampleService {
/**
* 使用默认虚拟线程执行器
*/
@Async
public CompletableFuture<String> asyncMethod1() {
log.info("当前线程: {}, 是否为虚拟线程: {}",
Thread.currentThread().getName(),
Thread.currentThread().isVirtual());
// 执行异步任务
return CompletableFuture.completedFuture("完成");
}
/**
* 指定使用虚拟线程执行器
*/
@Async("virtualThreadExecutor")
public CompletableFuture<String> asyncMethod2() {
log.info("当前线程: {}, 是否为虚拟线程: {}",
Thread.currentThread().getName(),
Thread.currentThread().isVirtual());
return CompletableFuture.completedFuture("完成");
}
}
1.2 Web请求处理中使用虚拟线程
Spring Boot 3.2+ 启用虚拟线程后,所有Web请求会自动使用虚拟线程处理,无需额外配置。
验证方式:
java
@RestController
@RequestMapping("/api/test")
@Slf4j
public class VirtualThreadTestController {
@GetMapping("/virtual-thread")
public Map<String, Object> testVirtualThread() {
Thread currentThread = Thread.currentThread();
Map<String, Object> result = new HashMap<>();
result.put("threadName", currentThread.getName());
result.put("isVirtual", currentThread.isVirtual());
result.put("threadId", currentThread.threadId());
log.info("请求处理线程: {}, 是否为虚拟线程: {}",
currentThread.getName(), currentThread.isVirtual());
return result;
}
}
1.3 定时任务中使用虚拟线程
配置类改造:
java
/**
* 定时任务配置(虚拟线程版本)
*
* @author
* @date 2024-10-31
*/
@Slf4j
@Configuration
@EnableScheduling
@ConditionalOnProperty(name = "spring.threads.virtual.enabled", havingValue = "true")
public class VirtualThreadSchedulingConfig implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setScheduler(taskScheduler());
}
@Bean
public Executor taskScheduler() {
return Executors.newVirtualThreadPerTaskExecutor();
}
}
使用示例:
java
@Component
@Slf4j
public class ScheduledTaskExample {
/**
* 定时任务会自动使用虚拟线程执行器
*/
@Scheduled(fixedRate = 5000)
public void scheduledTask() {
log.info("定时任务执行 - 线程: {}, 是否为虚拟线程: {}",
Thread.currentThread().getName(),
Thread.currentThread().isVirtual());
}
}
方式二:手动创建虚拟线程执行器
2.1 改进现有的VirtualThreadUtils工具类
java
/**
* 虚拟线程工具类(改进版)
*
* @author
* @date 2024-10-31
*/
@Slf4j
@Component
public class VirtualThreadUtils {
private static final ExecutorService EXECUTOR = Executors.newVirtualThreadPerTaskExecutor();
/**
* 执行虚拟线程任务
*
* @param task 任务
* @return Future
*/
public static Future<?> exeVirtualThread(Runnable task) {
return EXECUTOR.submit(() -> {
try {
log.debug("虚拟线程执行任务 - 线程: {}, 是否为虚拟线程: {}",
Thread.currentThread().getName(),
Thread.currentThread().isVirtual());
task.run();
} catch (Exception e) {
log.error("虚拟线程执行任务异常", e);
throw e;
}
});
}
/**
* 执行虚拟线程任务(带返回值)
*
* @param task 任务
* @param <T> 返回值类型
* @return Future
*/
public static <T> Future<T> exeVirtualThread(java.util.concurrent.Callable<T> task) {
return EXECUTOR.submit(() -> {
try {
log.debug("虚拟线程执行任务 - 线程: {}, 是否为虚拟线程: {}",
Thread.currentThread().getName(),
Thread.currentThread().isVirtual());
return task.call();
} catch (Exception e) {
log.error("虚拟线程执行任务异常", e);
throw e;
}
});
}
@PreDestroy
public void destroy() {
log.info("关闭虚拟线程执行器");
EXECUTOR.shutdown();
try {
if (!EXECUTOR.awaitTermination(60, TimeUnit.SECONDS)) {
EXECUTOR.shutdownNow();
if (!EXECUTOR.awaitTermination(60, TimeUnit.SECONDS)) {
log.error("虚拟线程执行器未能正常关闭");
}
}
} catch (InterruptedException e) {
EXECUTOR.shutdownNow();
Thread.currentThread().interrupt();
}
}
}
2.2 在CompletableFuture中使用虚拟线程
java
@Service
@Slf4j
public class CompletableFutureExample {
/**
* 使用虚拟线程执行器执行CompletableFuture
*/
public CompletableFuture<List<String>> processDataAsync() {
Executor virtualExecutor = Executors.newVirtualThreadPerTaskExecutor();
CompletableFuture<List<String>> future = CompletableFuture
.supplyAsync(() -> {
log.info("异步任务执行 - 线程: {}, 是否为虚拟线程: {}",
Thread.currentThread().getName(),
Thread.currentThread().isVirtual());
return fetchData();
}, virtualExecutor)
.thenApplyAsync(data -> {
log.info("处理数据 - 线程: {}, 是否为虚拟线程: {}",
Thread.currentThread().getName(),
Thread.currentThread().isVirtual());
return processData(data);
}, virtualExecutor);
return future;
}
private List<String> fetchData() {
// 模拟I/O操作
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return Arrays.asList("data1", "data2", "data3");
}
private List<String> processData(List<String> data) {
return data.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
}
}
方式三:为特定场景配置虚拟线程执行器
3.1 消息队列处理使用虚拟线程
java
/**
* RabbitMQ虚拟线程配置
*
* @author 往事随风去
* @date 2024-10-31
*/
@Slf4j
@Configuration
@ConditionalOnProperty(name = "spring.threads.virtual.enabled", havingValue = "true")
public class RabbitMQVirtualThreadConfig {
@Bean
public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(
ConnectionFactory connectionFactory) {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
// 使用虚拟线程执行器处理消息
factory.setTaskExecutor(new TaskExecutorAdapter(Executors.newVirtualThreadPerTaskExecutor()));
return factory;
}
}
3.2 数据库操作使用虚拟线程
对于MyBatis等数据库操作,通常不需要特别配置,因为:
- 数据库连接池(如Druid)会管理连接
- 虚拟线程在执行I/O阻塞操作时会自动释放平台线程
- 可以提高并发查询能力
四、最佳实践
1. 适用场景
虚拟线程适合:
- I/O密集型任务(数据库查询、HTTP请求、文件操作)
- 大量并发请求处理
- 异步任务处理
- Web请求处理
虚拟线程不适合:
- CPU密集型任务(计算密集型,应使用平台线程池)
- 需要线程本地存储(ThreadLocal)的场景(虚拟线程会频繁切换)
- 需要精确控制线程数量的场景
2. 注意事项
2.1 线程本地存储(ThreadLocal)
虚拟线程会频繁切换,ThreadLocal的使用需要注意:
java
// ❌ 不推荐:在虚拟线程中使用ThreadLocal存储大量数据
ThreadLocal<List<String>> threadLocal = new ThreadLocal<>();
// ✅ 推荐:使用ScopedValue(Java 21+)或谨慎使用ThreadLocal
ScopedValue<String> scopedValue = ScopedValue.newInstance();
2.2 线程池大小配置
启用虚拟线程后,不需要配置线程池大小,虚拟线程会自动管理:
yaml
# ❌ 不需要配置这些(虚拟线程会自动管理)
server:
tomcat:
threads:
max: 400 # 虚拟线程模式下无效
min-spare: 100 # 虚拟线程模式下无效
2.3 监控和调试
虚拟线程的监控需要使用新的API:
java
@Service
@Slf4j
public class VirtualThreadMonitor {
@Scheduled(fixedRate = 60000)
public void monitorVirtualThreads() {
ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
long[] threadIds = threadBean.getAllThreadIds();
long virtualThreadCount = Arrays.stream(threadIds)
.mapToObj(id -> {
ThreadInfo info = threadBean.getThreadInfo(id);
return info != null ? Thread.ofPlatform().getThreadGroup()
.findThread(id) : null;
})
.filter(Objects::nonNull)
.filter(Thread::isVirtual)
.count();
log.info("虚拟线程数量: {}", virtualThreadCount);
}
}
3. 性能优化建议
3.1 混合使用平台线程和虚拟线程
java
@Configuration
public class HybridThreadConfig {
/**
* CPU密集型任务使用平台线程池
*/
@Bean("cpuIntensiveExecutor")
public Executor cpuIntensiveExecutor() {
int processors = Runtime.getRuntime().availableProcessors();
return Executors.newFixedThreadPool(processors);
}
/**
* I/O密集型任务使用虚拟线程
*/
@Bean("ioIntensiveExecutor")
public Executor ioIntensiveExecutor() {
return Executors.newVirtualThreadPerTaskExecutor();
}
}
3.2 避免在虚拟线程中执行长时间CPU计算
java
@Service
public class TaskService {
@Autowired
@Qualifier("cpuIntensiveExecutor")
private Executor cpuExecutor;
@Autowired
@Qualifier("ioIntensiveExecutor")
private Executor ioExecutor;
public void processTask() {
// I/O操作使用虚拟线程
CompletableFuture<String> data = CompletableFuture
.supplyAsync(this::fetchData, ioExecutor);
// CPU计算使用平台线程
CompletableFuture<String> result = data
.thenApplyAsync(this::heavyComputation, cpuExecutor);
}
}
五、迁移建议
1. 逐步迁移
-
第一步:启用虚拟线程
yamlspring: threads: virtual: enabled: true -
第二步:改造异步配置
- 将线程池配置类改造为使用虚拟线程
- 将
ScheduledTaskCofiguration改造为使用虚拟线程
-
第三步:测试和验证
- 验证Web请求是否使用虚拟线程
- 验证异步方法是否使用虚拟线程
- 验证定时任务是否使用虚拟线程
-
第四步:性能测试
- 对比启用虚拟线程前后的性能
- 监控内存使用情况
- 监控线程数量
2. 回滚方案
如果出现问题,可以快速回滚:
yaml
spring:
threads:
virtual:
enabled: false # 禁用虚拟线程,恢复传统线程池
六、总结
虚拟线程在Spring Boot中的正确使用方式:
- 启用方式 :配置
spring.threads.virtual.enabled=true - Web请求:自动使用虚拟线程处理(无需额外配置)
- 异步方法 :通过
@Async自动使用虚拟线程 - 定时任务 :需要配置
SchedulingConfigurer - 手动创建 :使用
Executors.newVirtualThreadPerTaskExecutor() - 注意事项:避免CPU密集型任务,谨慎使用ThreadLocal
通过合理使用虚拟线程,可以显著提高I/O密集型应用的并发处理能力,减少线程资源消耗。