虚拟线程在Spring Boot中的正确使用方式

一、概述

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等数据库操作,通常不需要特别配置,因为:

  1. 数据库连接池(如Druid)会管理连接
  2. 虚拟线程在执行I/O阻塞操作时会自动释放平台线程
  3. 可以提高并发查询能力

四、最佳实践

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. 逐步迁移

  1. 第一步:启用虚拟线程

    yaml 复制代码
    spring:
      threads:
        virtual:
          enabled: true
  2. 第二步:改造异步配置

    • 将线程池配置类改造为使用虚拟线程
    • ScheduledTaskCofiguration 改造为使用虚拟线程
  3. 第三步:测试和验证

    • 验证Web请求是否使用虚拟线程
    • 验证异步方法是否使用虚拟线程
    • 验证定时任务是否使用虚拟线程
  4. 第四步:性能测试

    • 对比启用虚拟线程前后的性能
    • 监控内存使用情况
    • 监控线程数量

2. 回滚方案

如果出现问题,可以快速回滚:

yaml 复制代码
spring:
  threads:
    virtual:
      enabled: false  # 禁用虚拟线程,恢复传统线程池

六、总结

虚拟线程在Spring Boot中的正确使用方式:

  1. 启用方式 :配置 spring.threads.virtual.enabled=true
  2. Web请求:自动使用虚拟线程处理(无需额外配置)
  3. 异步方法 :通过 @Async 自动使用虚拟线程
  4. 定时任务 :需要配置 SchedulingConfigurer
  5. 手动创建 :使用 Executors.newVirtualThreadPerTaskExecutor()
  6. 注意事项:避免CPU密集型任务,谨慎使用ThreadLocal

通过合理使用虚拟线程,可以显著提高I/O密集型应用的并发处理能力,减少线程资源消耗。

相关推荐
q***985236 分钟前
VS Code 中如何运行Java SpringBoot的项目
java·开发语言·spring boot
allbs41 分钟前
spring boot项目excel导出功能封装——3.图表导出
spring boot·后端·excel
wasp5201 小时前
Spring AI 代码分析(十)--Spring Boot集成
人工智能·spring boot·spring
unclecss1 小时前
把 Spring Boot 的启动时间从 3 秒打到 30 毫秒,内存砍掉 80%,让 Java 在 Serverless 时代横着走
java·jvm·spring boot·serverless·graalvm
Billow_lamb2 小时前
Spring Boot2.x.x 全局错误处理
java·spring boot·后端
雁于飞2 小时前
分布式基础
java·spring boot·分布式·spring·wpf·cloud native
bing_1583 小时前
Spring Boot 项目中判断集合(List、Set、Map)不能为空且不为 null的注解使用
spring boot·后端·list
Q_Q5110082855 小时前
python+django/flask的结合人脸识别和实名认证的校园论坛系统
spring boot·python·django·flask·node.js·php
Q_Q5110082855 小时前
python+django/flask的选课系统与课程评价整合系统
spring boot·python·django·flask·node.js·php
老华带你飞5 小时前
社区养老保障|智慧养老|基于springboot+小程序社区养老保障系统设计与实现(源码+数据库+文档)
java·数据库·vue.js·spring boot·小程序·毕设·社区养老保障