Spring Boot 3.0配合Java 21正式引入虚拟线程(Virtual Thread)支持。虚拟线程是Project Loom的核心特性,实现了用户态轻量级线程调度,单JVM可创建数百万个虚拟线程。相比传统平台线程(OS线程),虚拟线程在IO密集型场景下显著提升并发能力,同时保持同步编程模型的简洁性。
虚拟线程与平台线程架构对比
平台线程直接映射OS线程,创建和切换成本高,每个线程占用约1MB栈空间。线程池通过复用线程降低创建开销,但池大小受限于物理资源(通常数百到数千)。
虚拟线程由JVM调度器管理,挂载到Carrier Thread(载体线程)上执行。当虚拟线程遇到IO阻塞时,JVM自动将其从载体线程卸载,载体线程继续执行其他虚拟线程。虚拟线程的栈存储在堆内存中,初始占用约几百字节,按需扩展。
两种线程的关键参数对比:
// 创建平台线程Thread platformThread = new Thread(() -> { // 1MB栈空间,1:1映射OS线程});platformThread.start();// 创建虚拟线程Thread virtualThread = Thread.ofVirtual() .name("my-virtual-thread") .start(() -> { // 初始栈空间KB级,可创建数百万个 });// 通过Executors创建虚拟线程工厂try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { for (int i = 0; i < 10000; i++) { executor.submit(() -> { Thread.sleep(Duration.ofSeconds(1)); return "done"; }); }} // 10000个虚拟线程同时sleep,仅占用少量载体线程
newVirtualThreadPerTaskExecutor为每个任务创建一个虚拟线程,无需配置线程池大小。10000个虚拟线程同时sleep时,底层可能只使用几十个载体线程,内存占用远低于10000个平台线程。
Spring Boot 3.0虚拟线程配置启用
Spring Boot 3.0+在Tomcat/Jetty/Netty等Servlet容器上自动支持虚拟线程。启用方式仅需一行配置:
# application.propertiesspring.threads.virtual.enabled=true
启用后,Spring MVC的每个HTTP请求由虚拟线程处理,而非从线程池获取平台线程。对于高并发的IO密集型Controller(如调用外部API、数据库查询),效果显著。
验证虚拟线程是否生效:
@RestControllerpublic class ThreadCheckController { @GetMapping("/thread-info") public Map<String, Object> threadInfo() { Thread current = Thread.currentThread(); return Map.of( "name", current.getName(), "virtual", current.isVirtual(), "class", current.getClass().getName() ); }}// 响应示例:// {"name":"","virtual":true,"class":"java.lang.VirtualThread"}// {"name":"http-nio-8080-exec-1","virtual":false,"class":"java.lang.Thread"}// 第二行为未启用虚拟线程时的输出
手动配置虚拟线程执行器(适用于异步任务场景):
@Configurationpublic class VirtualThreadConfig { @Bean public AsyncTaskExecutor virtualThreadExecutor() { return new TaskExecutorAdapter( Executors.newVirtualThreadPerTaskExecutor() ); } @Bean public TomcatProtocolHandlerCustomizer<?> protocolHandlerVirtualThreadExecutorCustomizer() { return protocolHandler -> { protocolHandler.setExecutor( Executors.newVirtualThreadPerTaskExecutor() ); }; }}
@Async方法使用虚拟线程:
@Servicepublic class OrderService { @Async("virtualThreadExecutor") public CompletableFuture<Order> processOrder(OrderRequest req) { // 在虚拟线程中执行,不占用平台线程 Order order = createOrder(req); notifyWarehouse(order); sendConfirmation(order); return CompletableFuture.completedFuture(order); }}
虚拟线程中的IO阻塞与synchronized锁问题
虚拟线程在IO阻塞(Socket read/write、HTTP调用、数据库查询)时会自动卸载,释放载体线程。但synchronized代码块中的阻塞不会卸载虚拟线程,导致载体线程被钉住(pinning),这是虚拟线程的主要陷阱。
问题场景:
// 问题代码:synchronized中IO阻塞会pin载体线程public synchronized String fetchData(String url) throws IOException { // synchronized锁持有期间,IO阻塞不卸载虚拟线程 // 载体线程被钉住,无法执行其他虚拟线程 return httpClient.get(url);}// 解决方案:使用ReentrantLock替代synchronizedprivate final ReentrantLock lock = new ReentrantLock();public String fetchData(String url) throws IOException, InterruptedException { lock.lock(); try { return httpClient.get(url); // IO阻塞时虚拟线程正常卸载 } finally { lock.unlock(); }}
检测载体线程pinning问题:
# JVM启动参数,输出pinning事件java -Djdk.tracePinnedThreads=full -jar app.jar# 输出示例:# java.lang.VirtualThread@7a81197d pinned on Thread[#34,ForkJoinPool-1-worker-2]# at com.example.OrderService.fetchData(OrderService.java:45)# - locked java.lang.Object@2c7b8de4 ← 元凶:synchronized块
tracePinnedThreads=short只输出摘要信息,=full输出完整堆栈。生产环境建议先用short模式排查,确认无严重pinning后再切换为full模式精确定位。
虚拟线程性能测试与基准对比
IO密集型场景(HTTP请求调用外部API,响应时间约100ms)的基准测试:
// 测试代码:模拟1000并发请求@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)class ConcurrencyTest { @Autowired TestRestTemplate restTemplate; @Test void testPlatformThread() throws Exception { // spring.threads.virtual.enabled=false int concurrency = 1000; long start = System.currentTimeMillis(); List<CompletableFuture<Void>> futures = IntStream.range(0, concurrency) .mapToObj(i -> CompletableFuture.runAsync(() -> restTemplate.getForObject("/api/external-call", String.class) )) .collect(Collectors.toList()); CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join(); long elapsed = System.currentTimeMillis() - start; System.out.println("Platform thread: " + elapsed + "ms"); } @Test void testVirtualThread() throws Exception { // spring.threads.virtual.enabled=true // 相同测试逻辑 }}
实测结果(4核8G环境,1000并发,每个请求IO等待100ms):
平台线程(默认线程池200):总耗时约550ms,线程池接近饱和,部分请求排队等待。
虚拟线程:总耗时约120ms,接近IO等待时间,1000个虚拟线程几乎同时发起请求。
CPU密集型场景(纯计算):虚拟线程无优势,因为计算过程不阻塞,虚拟线程和平台线程表现相当。虚拟线程的本质优势在于IO阻塞时的调度效率。
虚拟线程不适用于CPU密集型任务的原因:虚拟线程调度器使用ForkJoinPool作为载体线程池,默认大小为CPU核心数。当虚拟线程执行CPU密集型计算时不会卸载,等同于占用一个载体线程。大量CPU密集型虚拟线程会导致载体线程池耗尽,反而不如使用固定大小的平台线程池。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/springboot30-xu-ni-xian-cheng-bing-fa-mo-xing-projectloom/