系列:《Java Virtual Threads(虚拟线程)教程:从零上手到实战优化》
第 2 篇:使用 Virtual Threads 重构传统同步代码(HTTP / 数据库 / RPC 实战提升)
在第 1 篇中,我们理解了 Virtual Thread 的意义与特性,本篇将直接进入实战。
本篇目标非常明确:
把你现有必须用线程池的代码,用 Virtual Threads 重写成“更干净、更高性能、更容易维护”的版本。
并展示性能为何会显著提升。
一、传统线程池 vs 虚拟线程:本质区别
传统写法(通常是这样):
ExecutorService executor = Executors.newFixedThreadPool(200);
Future<String> result = executor.submit(() -> {
return httpCall();
});
缺点:
线程池大小有限(CPU & 内存限制)
大量 I/O wait 时,线程被阻塞,整体并发能力差
必须写 Future/Callback,可读性差
使用虚拟线程以后:
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
executor.submit(() -> httpCall());
}
优点立刻显现:
你可以开 100000+ 线程 都没问题
“阻塞”=挂起,不占用平台线程
代码保持同步风格,非常易读
二、实战 1:使用虚拟线程优化 HTTP 请求并发
这里以 Java 21 的 HttpClient 为例。
2.1 传统线程池版(需要 Future + 线程池)
ExecutorService executor = Executors.newFixedThreadPool(200);
HttpClient client = HttpClient.newHttpClient();
List<Future<String>> futures = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
futures.add(executor.submit(() -> {
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create("https://example.com"))
.build();
return client.send(req, HttpResponse.BodyHandlers.ofString()).body();
}));
}
for (Future<String> f : futures) {
System.out.println(f.get());
}
缺点:
线程池 200 并发 → 只能同时处理 200 个请求
请求慢时线程被阻塞 → 池子被占满
后续任务排队 → 延迟高
2.2 使用虚拟线程重写(1000 个任务 = 1000 个线程)
HttpClient client = HttpClient.newHttpClient();
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
List<Future<String>> futures = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
futures.add(executor.submit(() -> {
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create("https://example.com"))
.build();
return client.send(req, HttpResponse.BodyHandlers.ofString()).body();
}));
}
for (Future<String> f : futures) {
System.out.println(f.get());
}
}
为什么它更快?
因为在虚拟线程里:
HTTP 请求等待时间 ≠ 占用平台线程
只在真正运行 Java 代码时占用 CPU
等待网络 I/O → 虚拟线程自动挂起,不影响其他任务
同样是同步写法,吞吐量却接近异步 WebClient/Netty 的水平。
三、实战 2:数据库并发查询优化(重要)
数据库访问也是 I/O(网络、磁盘),非常适合虚拟线程。
3.1 传统:线程池版本
ExecutorService executor = Executors.newFixedThreadPool(50);
Future<List<User>> f = executor.submit(() -> {
return jdbcTemplate.query("SELECT * FROM user", new UserMapper());
});
List<User> users = f.get();
缺点:
DB 查询慢,线程被阻塞
线程池必须限制大小:太大线程切换成本高
3.2 虚拟线程版本(推荐写法)
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
Future<List<User>> f = executor.submit(() -> {
return jdbcTemplate.query("SELECT * FROM user", new UserMapper());
});
List<User> users = f.get();
}
收益:
可同时开启成百上千 DB 查询,而不会吃掉 CPU
无需调优线程池
单个阻塞不会影响整体吞吐
3.3 批量数据库查询(典型业务场景)
场景:需要一次性查询 1000 个用户详情
传统线程池一般只能开几十个线程 → 查询慢
虚拟线程写法:
try (var exec = Executors.newVirtualThreadPerTaskExecutor()) {
List<Future<User>> futures = new ArrayList<>();
for (Long userId : userIds) {
futures.add(exec.submit(() -> {
return jdbcTemplate.queryForObject(
"SELECT * FROM user WHERE id = ?", new UserMapper(), userId
);
}));
}
List<User> results = new ArrayList<>();
for (Future<User> f : futures) {
results.add(f.get());
}
}
这种写法看似普通,但吞吐量巨大提升:
传统线程池:50 并发
虚拟线程:1000+ 并发
毫无压力、性能极高
四、实战 3:RPC/微服务调用优化
假设你要同时请求多个微服务接口:
获取用户详情
获取用户订单
获取用户积分
获取用户等级
4.1 传统写法(CompletableFuture 版本)
CompletableFuture<User> userFuture = CompletableFuture.supplyAsync(() -> userService.getUser(id));
CompletableFuture<List<Order>> orderFuture = CompletableFuture.supplyAsync(() -> orderService.getOrders(id));
CompletableFuture<Point> pointFuture = CompletableFuture.supplyAsync(() -> pointService.getPoints(id));
User user = userFuture.join();
List<Order> orders = orderFuture.join();
Point points = pointFuture.join();
缺点:
写法复杂,可读性差
调优线程池成本高
4.2 虚拟线程版(同步写法反而更快)
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
Future<User> userFuture = executor.submit(() -> userService.getUser(id));
Future<List<Order>> orderFuture = executor.submit(() -> orderService.getOrders(id));
Future<Point> pointFuture = executor.submit(() -> pointService.getPoints(id));
User user = userFuture.get();
List<Order> orders = orderFuture.get();
Point points = pointFuture.get();
}
更爽的写法(不使用 Future):
User user = Thread.ofVirtual().unstarted(() -> userService.getUser(id)).start().join();
五、使用虚拟线程最佳写法(官方推荐)
Java 官方推荐这样写:
每个任务一个虚拟线程 Executor
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
executor.submit(task);
}
优势:
自动为每个 task 创建独立 Virtual Thread
不用自己管理生命周期
线程泄漏风险极低
六、性能分析(为什么吞吐量暴涨)
虚拟线程最大的价值:
让同步阻塞变成“无成本等待”,让 I/O 密集型服务吞吐量几乎线性提升。
原因:
传统线程:阻塞 = 占用 OS Thread
虚拟线程:阻塞 = 让出 OS Thread(挂起),其他任务继续执行
因此:
DB、Redis、HTTP、RPC 都是 I/O → 全部大幅提升性能
用同步写法实现异步运行 → 开发效率提高
七、本篇总结
本篇解决了 Virtual Threads 最关键的价值:
✔ 同步写法(阻塞风格)能够获得异步性能
✔ 如何用虚拟线程重写 HTTP / DB / RPC 并发请求
✔ 代码极度简化,可读性提高
✔ 每个任务一个线程,性能更好,设计更简单
✔ 不需要线程池调优
下一篇预告(第 3 篇)
《虚拟线程调度机制深度解析:平台线程、纤程、栈帧、挂起与恢复》
内容包括:
虚拟线程到底如何挂起?
Platform Thread 与 Virtual Thread 是怎么协作的?
Virtual Thread 的“栈”是怎么管理的?
为什么同步 I/O 能跑出异步性能?
Pinning(线程钉住)为何会影响性能?(非常重要)
默认评论
Halo系统提供的评论