上周在项目里遇到个问题,折腾了两天终于搞定了

我们有个IO密集型的接口,并发一上来线程池就扛不住。试过加线程数,效果不明显;换过不同队列策略,也不行。后来听说Java 21的虚拟线程专门解决这类问题,就决定试试。

虚拟线程到底是什么

先说结论:虚拟线程是JVM层面的轻量级线程,由调度器映射到少量平台线程上运行

跟传统线程池最大的区别:

特性 平台线程 虚拟线程
创建成本 ~1KB栈内存 ~几百字节
百万级创建 OOM 没问题
阻塞操作 占用平台线程 自动卸载

一句话:如果你的业务大部分时间都在等DB、等HTTP、等MQ,虚拟线程就是为你准备的

Spring Boot 3 开启虚拟线程

就这么一行?对,但实际用起来有不少细节要注意。

完整可运行代码

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project>
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.5</version>
    </parent>
    <groupId>com.example</groupId>
    <artifactId>virtual-thread-demo</artifactId>
    <version>1.0.0</version>
    <properties>
        <java.version>21</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

application.yml

server:
  port: 8080
spring:
  threads:
    virtual:
      enabled: true

主启动类

package com.example.vt;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class VtApp {
    public static void main(String[] args) {
        SpringApplication.run(VtApp.class, args);
    }
}

IO密集型服务模拟

package com.example.vt.service;
import org.springframework.stereotype.Service;
import java.util.concurrent.*;
import java.util.ArrayList;

@Service
public class IoService {
    public String dbQuery(String id, int delayMs) {
        boolean vt = Thread.currentThread().isVirtual();
        try { Thread.sleep(delayMs); }
        catch (InterruptedException e) { Thread.currentThread().interrupt(); return " interrupted"; }
        return "[vt=" + vt + "] DB-" + id + "-ok";
    }

    public CompletableFuture<String> parallelQuery(int count) {
        var futures = new ArrayList<CompletableFuture<String>>();
        for (int i = 0; i < count; i++) {
            final int idx = i;
            futures.add(CompletableFuture.supplyAsync(() -> dbQuery("q-" + idx, 30 + idx % 5 * 15)));
        }
        return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
            .thenApply(v -> { StringBuilder sb = new StringBuilder();
                for (var f : futures) sb.append(f.join()).append("|"); return sb.toString(); });
    }
}

性能对比测试

package com.example.vt.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.Duration;
import java.time.Instant;
import java.util.*;

@Service
public class BenchService {
    @Autowired private IoService ioService;

    public String runCompare(int taskCount) {
        var r = new StringBuilder();
        r.append("===== 对比测试 (" + taskCount + "任务) =====\n");
        int[] sizes = {4, 8, 16, 32, 64};
        for (int s : sizes) {
            var pool = Executors.newFixedThreadPool(s);
            var start = Instant.now();
            var futs = new ArrayList<Future<String>>();
            for (int i = 0; i < taskCount; i++) {
                final int idx = i;
                futs.add(pool.submit(() -> ioService.dbQuery("tp-" + idx, 30)));
            }
            for (var f : futs) { try { f.get(); } catch(Exception e){} }
            pool.shutdown();
            r.append("[ThreadPool-" + s + "] " + taskCount + "任务 -> " +
              Duration.between(start, Instant.now()).toMillis() + "ms\n");
        }
        var vtp = Executors.newVirtualThreadPerTaskExecutor();
        var start2 = Instant.now();
        var vfuts = new ArrayList<Future<String>>();
        for (int i = 0; i < taskCount; i++) {
            final int idx = i;
            vfuts.add(vtp.submit(() -> ioService.dbQuery("vt-" + idx, 30)));
        }
        for (var f : vfuts) { try { f.get(); } catch(Exception e){} }
        vtp.close();
        r.append("[VirtualThreads] " + taskCount + "任务 -> " +
          Duration.between(start2, Instant.now()).toMillis() + "ms\n");
        return r.toString();
    }
}

REST控制器

package com.example.vt.controller;
import com.example.vt.service.BenchService;
import com.example.vt.service.IoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.concurrent.CompletableFuture;
import java.util.Map;

@RestController
@RequestMapping("/api/demo")
public class DemoCtrl {
    @Autowired private IoService ioService;
    @Autowired private BenchService benchService;

    @GetMapping("/io/{id}")
    public ResponseEntity<?> singleIo(@PathVariable String id) {
        return ResponseEntity.ok(Map.of(
            "id", id, "data", ioService.dbQuery(id, 80),
            "isVirtual", Thread.currentThread().isVirtual(),
            "thread", Thread.currentThread().getName()
        ));
    }

    @GetMapping("/parallel-io")
    public CompletableFuture<ResponseEntity<?>> parallelIo(@RequestParam(defaultValue="20") int count) {
        return ioService.parallelQuery(count).thenApply(ResponseEntity::ok);
    }

    @GetMapping("/bench")
    public ResponseEntity<?> bench(@RequestParam(defaultValue="500") int n) {
        return ResponseEntity.ok(benchService.runCompare(n));
    }
}

测试类

package com.example.vt;
import com.example.vt.service.IoService;
import com.example.vt.service.BenchService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import static org.assertj.core.api.Assertions.*;

@SpringBootTest
class VtAppTests {
    @Autowired private IoService ioService;
    @Autowired private BenchService benchService;

    @Test void testDbQuery() { assertThat(ioService.dbQuery("test", 10)).contains("test"); }
    @Test void testParallel() throws Exception { assertThat(ioService.parallelQuery(15).get()).contains("DB"); }
    @Test void testBench() { System.out.println(benchService.runCompare(200)); }
}

我踩过的坑

坑1:synchronized会钉死平台线程

虚拟线程遇到synchronized会钉住底层平台线程。所以:

// 错误做法
public synchronized void badMethod() { ... }

// 正确做法 — 用ReentrantLock替代
private final ReentrantLock lock = new ReentrantLock();
public void goodMethod() { lock.lock(); try {} finally { lock.unlock(); } }

坑2:不要池化虚拟线程

// 不要这样
Executors.newFixedThreadPool(200, factory);
// 这样就好
Executors.newVirtualThreadPerTaskExecutor();

坑3:数据库连接池要调大

因为虚拟线程支撑更多并发请求,连接池可能成为新瓶颈。

总结

虚拟线程不是银弹,但在IO密集型场景确实能简化并发模型。核心优势就三个字:不用调参

环境要求:JDK 21+, Spring Boot 3.2+
完整代码:以上可直接运行

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐