虚拟线程在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密集型应用的并发处理能力,减少线程资源消耗。

相关推荐
麦兜*6 小时前
Spring Boot 应用 Docker 监控:Prometheus + Grafana 全方位监控
spring boot·后端·spring cloud·docker·prometheus
L.EscaRC7 小时前
Redisson在Spring Boot中的高并发应用解析
java·spring boot·后端
Naylor7 小时前
玩转kafka
spring boot·kafka
摇滚侠7 小时前
Spring Boot3零基础教程,StreamAPI 介绍,笔记98
java·spring boot·笔记
摇滚侠7 小时前
Spring Boot3零基础教程,StreamAPI 的基本用法,笔记99
java·spring boot·笔记
codingPower8 小时前
升级mybatis-plus导致项目启动报错: net.sf.jsqlparser.statement.select.SelectBody
java·spring boot·maven·mybatis
刘一说9 小时前
深入理解 Spring Boot Web 开发中的全局异常统一处理机制
前端·spring boot·后端
智_永无止境10 小时前
Spring Boot全局异常处理指南
java·spring boot
屹奕10 小时前
基于EasyExcel实现Excel导出功能
java·开发语言·spring boot·excel