测试类探索 传统线程池和虚拟线程的性能对比
测试背景
模拟两个微服务的调用 去创建订单 Order 需要调用服务1 ProductService(内部sleep30ms 触发yield) 服务2 UserService (内部sleep50ms 触发yield) 首先获取到user 和 product 然后 构建Order
测试构建
以下是项目的具体结构: 
配置类:配置Tomcat工作线程去使用虚拟线程
typescript
@Configuration
public class VirtualThreadConfig {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
@Bean
public TomcatProtocolHandlerCustomizer protocolHandlerCustomizer() {
return protocolHandler -> {
protocolHandler.setExecutor(Executors.newVirtualThreadPerTaskExecutor());
};
}
}
12345678910111213141516
各种models:
vbnet
public record Order(Long id, Long userId, Long productId, BigDecimal totalPrice) {}
1
csharp
public record OrderRequest(Long userId, Long productId) {}
1
arduino
public record Product(Long id, String name, BigDecimal price) {}
1
arduino
public record User(Long id, String name, String email) {}
1
OrderService:
scss
@Service
public class OrderService {
private static final Logger log = LoggerFactory.getLogger(OrderService.class);
private final UserServiceClient userServiceClient;
private final ProductServiceClient productServiceClient;
public OrderService(UserServiceClient userServiceClient,
ProductServiceClient productServiceClient) {
this.userServiceClient = userServiceClient;
this.productServiceClient = productServiceClient;
}
public Order createOrder(Long userId, Long productId) {
log.info("开始创建订单 - 线程: {}", Thread.currentThread());
User user = userServiceClient.getUser(userId);
Product product = productServiceClient.getProduct(productId);
Order order = new Order(
System.currentTimeMillis(),
user.id(),
product.id(),
product.price()
);
log.info("订单创建完成 - 线程: {}", Thread.currentThread());
return order;
}
public Order createOrderParallel(Long userId, Long productId) {
log.info("开始并行创建订单 - 线程: {}", Thread.currentThread());
try (var virtualExecutor = Executors.newVirtualThreadPerTaskExecutor()) {
CompletableFuture userFuture = CompletableFuture.supplyAsync(
() -> userServiceClient.getUser(userId),
virtualExecutor
);
CompletableFuture
productFuture = CompletableFuture.supplyAsync(
() -> productServiceClient.getProduct(productId),
virtualExecutor
);
User user = userFuture.join();
Product product = productFuture.join();
Order order = new Order(
System.currentTimeMillis(),
user.id(),
product.id(),
product.price()
);
log.info("并行订单创建完成 - 线程: {}", Thread.currentThread());
return order;
}
}
public Order createOrderParallelWithOutVirtual(Long userId, Long productId) {
log.info("开始并行创建订单(不使用虚拟线程) - 线程: {}", Thread.currentThread());
CompletableFuture userFuture = CompletableFuture.supplyAsync(
() -> userServiceClient.getUser(userId)
);
CompletableFuture productFuture = CompletableFuture.supplyAsync(
() -> productServiceClient.getProduct(productId)
);
User user = userFuture.join();
Product product = productFuture.join();
Order order = new Order(
System.currentTimeMillis(),
user.id(),
product.id(),
product.price()
);
log.info("并行订单创建完成(不使用虚拟线程) - 线程: {}", Thread.currentThread());
return order;
}
}
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
模拟的两个微服务客户端:
java
@Component
public class UserServiceClient {
private static final Logger log = LoggerFactory.getLogger(UserServiceClient.class);
private final RestTemplate restTemplate;
public UserServiceClient(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
public User getUser(Long userId) {
log.info("调用用户服务 - 线程: {}", Thread.currentThread());
try {
Thread.sleep(50);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
User user = new User(userId, "User" + userId, "user" + userId + "@example.com");
log.info("用户服务调用完成 - 线程: {}", Thread.currentThread());
return user;
}
}
1234567891011121314151617181920212223242526272829
java
@Component
public class ProductServiceClient {
private static final Logger log = LoggerFactory.getLogger(ProductServiceClient.class);
private final RestTemplate restTemplate;
public ProductServiceClient(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
public Product getProduct(Long productId) {
log.info("调用商品服务 - 线程: {}", Thread.currentThread());
try {
Thread.sleep(30);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
Product product = new Product(productId, "Product" + productId, new BigDecimal("99.99"));
log.info("商品服务调用完成 - 线程: {}", Thread.currentThread());
return product;
}
}
1234567891011121314151617181920212223242526
控制测试类:
ini
@RestController
@RequestMapping("/test")
public class PerformanceTestController {
private static final Logger log = LoggerFactory.getLogger(PerformanceTestController.class);
private final OrderService orderService;
public PerformanceTestController(OrderService orderService) {
this.orderService = orderService;
}
@GetMapping("/concurrent/{count}")
public String testConcurrentOrders(@PathVariable int count) {
log.info("开始并发测试,订单数量: {}", count);
long startTime = System.currentTimeMillis();
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
List> futures = new ArrayList<>();
for (int i = 1; i future = CompletableFuture.supplyAsync(() -> {
return orderService.createOrder((long) orderId, (long) orderId);
}, executor);
futures.add(future);
}
List orders = futures.stream()
.map(CompletableFuture::join)
.toList();
long endTime = System.currentTimeMillis();
long duration = endTime - startTime;
log.info("并发测试完成: {} 个订单, 耗时: {} ms", count, duration);
return String.format("成功处理 %d 个订单,总耗时: %d ms,平均耗时: %.2f ms",
count, duration, (double) duration / count);
}
}
@GetMapping("/detailed-compare/{count}")
public String detailedCompare(@PathVariable int count) {
log.info("开始详细对比测试,订单数量: {}", count);
long virtualSerial = testVirtualThreadWithSerial(count);
long virtualParallel = testVirtualThreadWithVirtualParallel(count);
long threadPoolSerial = testThreadPoolWithSerial(count);
long threadPoolForkJoin = testThreadPoolWithForkJoin(count);
long forkJoinOnly = testForkJoinOnly(count);
return String.format(
"详细性能对比结果:\n" +
"1. 虚拟线程+串行: %d ms\n" +
"2. 虚拟线程+虚拟并行: %d ms\n" +
"3. 传统线程池+串行: %d ms\n" +
"4. 传统线程池+ForkJoin: %d ms\n" +
"5. 仅ForkJoin: %d ms\n" +
"ForkJoinPool并行度: %d",
virtualSerial, virtualParallel, threadPoolSerial,
threadPoolForkJoin, forkJoinOnly, ForkJoinPool.commonPool().getParallelism()
);
}
private long testVirtualThreadWithSerial(int count) {
long startTime = System.currentTimeMillis();
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
List> futures = IntStream.range(1, count + 1)
.mapToObj(i -> CompletableFuture.supplyAsync(() ->
orderService.createOrder((long) i, (long) i), executor))
.toList();
futures.forEach(CompletableFuture::join);
}
return System.currentTimeMillis() - startTime;
}
private long testVirtualThreadWithVirtualParallel(int count) {
long startTime = System.currentTimeMillis();
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
List> futures = IntStream.range(1, count + 1)
.mapToObj(i -> CompletableFuture.supplyAsync(() ->
orderService.createOrderParallel((long) i, (long) i), executor))
.toList();
futures.forEach(CompletableFuture::join);
}
return System.currentTimeMillis() - startTime;
}
private long testThreadPoolWithSerial(int count) {
long startTime = System.currentTimeMillis();
try (var executor = Executors.newFixedThreadPool(200)) {
List> futures = IntStream.range(1, count + 1)
.mapToObj(i -> CompletableFuture.supplyAsync(() ->
orderService.createOrder((long) i, (long) i), executor))
.toList();
futures.forEach(CompletableFuture::join);
}
return System.currentTimeMillis() - startTime;
}
private long testThreadPoolWithForkJoin(int count) {
long startTime = System.currentTimeMillis();
try (var executor = Executors.newFixedThreadPool(200)) {
List> futures = IntStream.range(1, count + 1)
.mapToObj(i -> CompletableFuture.supplyAsync(() ->
orderService.createOrderParallelWithOutVirtual((long) i, (long) i), executor))
.toList();
futures.forEach(CompletableFuture::join);
}
return System.currentTimeMillis() - startTime;
}
private long testForkJoinOnly(int count) {
long startTime = System.currentTimeMillis();
List> futures = IntStream.range(1, count + 1)
.mapToObj(i -> CompletableFuture.supplyAsync(() ->
orderService.createOrderParallelWithOutVirtual((long) i, (long) i)))
.toList();
futures.forEach(CompletableFuture::join);
return System.currentTimeMillis() - startTime;
}
@GetMapping("/virtual-thread-capacity/{count}")
public String testVirtualThreadCapacity(@PathVariable int count) {
log.info("开始虚拟线程容量测试,线程数量: {}", count);
long startTime = System.currentTimeMillis();
Runtime runtime = Runtime.getRuntime();
long initialMemory = runtime.totalMemory() - runtime.freeMemory();
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
List> futures = new ArrayList<>();
for (int i = 1; i future = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(100);
return "VirtualThread-" + threadId + " completed on " + Thread.currentThread();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return "Interrupted";
}
}, executor);
futures.add(future);
if (i % 1000 == 0) {
long currentMemory = runtime.totalMemory() - runtime.freeMemory();
log.info("已创建 {} 个虚拟线程,内存使用: {} MB",
i, (currentMemory - initialMemory) / 1024 / 1024);
}
}
List results = futures.stream()
.map(CompletableFuture::join)
.toList();
long endTime = System.currentTimeMillis();
long finalMemory = runtime.totalMemory() - runtime.freeMemory();
long duration = endTime - startTime;
return String.format(
"虚拟线程容量测试完成:\n" +
"线程数量: %d\n" +
"总耗时: %d ms\n" +
"初始内存: %d MB\n" +
"峰值内存: %d MB\n" +
"内存增长: %d MB\n" +
"平均每线程内存: %.2f KB",
count, duration,
initialMemory / 1024 / 1024,
finalMemory / 1024 / 1024,
(finalMemory - initialMemory) / 1024 / 1024,
(double)(finalMemory - initialMemory) / count / 1024
);
} catch (OutOfMemoryError e) {
return String.format("内存溢出!在创建 %d 个虚拟线程时达到内存限制", count);
}
}
@GetMapping("/thread-relationship/{count}")
public String testVirtualThreadRelationship(@PathVariable int count) {
log.info("开始测试虚拟线程与载体线程关系,请求数量: {}", count);
long startTime = System.currentTimeMillis();
List> futures = new ArrayList<>();
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
for (int i = 1; i future = CompletableFuture.supplyAsync(() -> {
Thread currentThread = Thread.currentThread();
String virtualThreadName = currentThread.getName();
long virtualThreadId = currentThread.getId();
String carrierThreadInfo = getCarrierThreadInfo(currentThread);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
String carrierThreadInfoAfter = getCarrierThreadInfo(currentThread);
String result = String.format(
"请求[%d] - 虚拟线程: %s (ID: %d)\n" +
" 载体线程(前): %s\n" +
" 载体线程(后): %s\n" +
" 载体线程变化: %s\n",
requestId, virtualThreadName, virtualThreadId,
carrierThreadInfo, carrierThreadInfoAfter,
!carrierThreadInfo.equals(carrierThreadInfoAfter) ? "是" : "否"
);
log.info(result.replace("\n", " | "));
return result;
}, executor);
futures.add(future);
}
List results = futures.stream()
.map(CompletableFuture::join)
.toList();
long endTime = System.currentTimeMillis();
long duration = endTime - startTime;
java.util.Map carrierThreadCount = new java.util.HashMap<>();
java.util.Map> carrierToVirtualMapping = new java.util.HashMap<>();
for (String result : results) {
String[] lines = result.split("\n");
if (lines.length >= 3) {
String carrierBefore = lines[1].trim();
String carrierAfter = lines[2].trim();
String virtualThread = lines[0].split(" - ")[1].split(" \\(")[0];
carrierThreadCount.merge(carrierBefore, 1, Integer::sum);
if (!carrierBefore.equals(carrierAfter)) {
carrierThreadCount.merge(carrierAfter, 1, Integer::sum);
}
carrierToVirtualMapping.computeIfAbsent(carrierBefore, k -> new java.util.HashSet<>()).add(virtualThread);
if (!carrierBefore.equals(carrierAfter)) {
carrierToVirtualMapping.computeIfAbsent(carrierAfter, k -> new java.util.HashSet<>()).add(virtualThread);
}
}
}
StringBuilder report = new StringBuilder();
report.append("=== 虚拟线程与载体线程关系测试报告 ===\n");
report.append(String.format("测试请求数: %d\n", count));
report.append(String.format("总耗时: %d ms\n", duration));
report.append(String.format("使用的载体线程数: %d\n", carrierThreadCount.size()));
report.append("\n=== 载体线程使用统计 ===\n");
carrierThreadCount.entrySet().stream()
.sorted(java.util.Map.Entry.comparingByValue().reversed())
.forEach(entry -> {
String carrierThread = entry.getKey();
int usageCount = entry.getValue();
java.util.Set virtualThreads = carrierToVirtualMapping.get(carrierThread);
report.append(String.format("%s: 使用%d次, 承载%d个虚拟线程\n",
carrierThread, usageCount, virtualThreads.size()));
});
report.append("\n=== 详细执行信息 ===\n");
results.forEach(report::append);
return report.toString();
} catch (Exception e) {
log.error("测试过程中发生异常", e);
return "测试失败: " + e.getMessage();
}
}
private String getCarrierThreadInfo(Thread virtualThread) {
try {
if (virtualThread.isVirtual()) {
String threadStr = virtualThread.toString();
if (threadStr.contains("@")) {
String carrierInfo = threadStr.substring(threadStr.indexOf("@") + 1);
return String.format("载体线程: %s", carrierInfo);
}
ThreadGroup group = virtualThread.getThreadGroup();
if (group != null) {
return String.format("载体线程组: %s", group.getName());
}
}
return String.format("平台线程: %s (ID: %d)",
virtualThread.getName(), virtualThread.getId());
} catch (Exception e) {
long threadId = virtualThread.getId();
String threadName = virtualThread.getName();
int carrierIndex = (int)(threadId % Runtime.getRuntime().availableProcessors());
return String.format("推测载体线程: ForkJoinPool-worker-%d (虚拟线程: %s)",
carrierIndex, threadName);
}
}
}
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370
application.yml
yaml
server:
port: 8082
logging:
level:
com.example.virtualthread: INFO
root: INFO
pattern:
console: "%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"
spring:
application:
name: virtual-thread-demo
threads:
virtual:
enabled: true
1234567891011121314151617
pom文件引入的依赖
bash
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-devtools
runtime
true
org.springframework.boot
spring-boot-starter-test
test
123456789101112131415161718
测试结果分析
有五种组合测试来探索 虚拟线程 vs 传统线程池的效率 重点关注组合【 1 和 3】 因为 组合【1 3】 是直接体现虚拟线程和传统线程池的区别 2、4 是为了对比 如果内部的fork join池也去使用虚拟线程池去构建会存在什么结果 对于1 、3 我们首先理论分析下
- 虚拟线程池 + 串行调用 执行层次: ├─ 第1层:虚拟线程池线 │ └─ 每个线程执行:createOrder() │ ├─ userServiceClient.getUser() - 50ms (在当前线程sleep) │ └─ productServiceClient.getProduct() - 30ms (在当前线程sleep)
瓶颈分析: 不存在线程池的限制 和本机内存有关 ForkJoinPool无关(没有使用)
1000个订单 ÷ 1000个虚拟线程 = 1批次 每批次80ms,总计:80ms
- 传统线程池 + 串行调用 执行层次: ├─ 第1层:200个固定线程池线程 │ └─ 每个线程执行:createOrder() │ ├─ userServiceClient.getUser() - 50ms (在当前线程sleep) │ └─ productServiceClient.getProduct() - 30ms (在当前线程sleep)
瓶颈分析: 主要瓶颈:线程池200个线程的限制 ForkJoinPool无关(没有使用)
1000个订单 ÷ 200个线程 = 5批次 每批次80ms,总计:5 × 80ms = 400ms
ini
@GetMapping("/detailed-compare/{count}")
public String detailedCompare(@PathVariable int count) {
log.info("开始详细对比测试,订单数量: {}", count);
long virtualSerial = testVirtualThreadWithSerial(count);
long virtualParallel = testVirtualThreadWithVirtualParallel(count);
long threadPoolSerial = testThreadPoolWithSerial(count);
long threadPoolForkJoin = testThreadPoolWithForkJoin(count);
long forkJoinOnly = testForkJoinOnly(count);
1234567891011121314151617181920
我们来看真实的执行效果: 
可以看到真实结果有近似5倍的提升 应正了理论上5倍的提升
理论 80ms->400oms 真实129ms ->470ms
瓶颈分析
好的我们继续分析所有的5种测试以及他们的对应的瓶颈
| 测试场景 | 线程层次 | 主要瓶颈 | 预期耗时 |
|---|---|---|---|
| 虚拟线程 + 串行 | 1000 个虚拟线程 | 无瓶颈 | ~80ms |
| 虚拟线程 + 虚拟并行 | 1000 个虚拟线程 → 2000 个虚拟线程 | 无瓶颈 | ~50ms (内部将30+50的串行优化为并发 所以预计时50ms) |
| 传统线程池 + 串行 | 200 个固定线程 | 200 线程限制 | ~400ms (80ms *5) |
| 传统线程池 + ForkJoin | 200 个固定线程 → 7 个 ForkJoin 线程 | ForkJoin 限制 | ~10秒 |
| 仅 ForkJoin | 7 个 ForkJoin 线程 | ForkJoin 限制 | ~10 秒 |

真实的执行效果基本匹配理论分析
测试二:每一个虚拟线程会占据多大的内存
bash
curl http://localhost:8082/test/virtual-thread-capacity/1000
1
可以看到平均的内存占用才3kb 相比于MB 级别的平台线程 提升很大
测试三:虚拟线程与载体线程的关系
arduino
curl "http://localhost:8082/test/thread-relationship/100"
1
可以看到100个请求 只需要8个载体线程就行
这里进一步的体现了 每一个虚拟线程在卸载 ->重新挂载 是另一个载体线程重新运行,而不是挂载的原始载体线程
总结
传统线程池+串行:200个线程是瓶颈 传统线程池+ForkJoin:ForkJoin的7个线程是更严重的瓶颈 双重瓶颈:200个外层线程 × 7个内层ForkJoin线程 = 实际并发度很低
协程概念介绍
协程(coroutine)是一种 轻量级线程模型,它与线程类似,但能主动挂起和恢复执行。核心特性如下: 可以挂起执行并保存状态 下一次可以从挂起处恢复(不重新创建) 不需要线程上下文切换的高成本 适合处理高并发 IO 场景(如服务端开发)
虚拟线程常用的使用场景和注意事项
1、不推荐使用虚拟线程池
csharp
public class VirtualThreadApproach {
public void virtualThreadWay() {
for (int i = 0; i {
doSomeWork();
});
}
}
public void virtualThreadPool() {
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
for (int i = 0; i {
doSomeWork();
});
}
}
}
}
12345678910111213141516171819202122232425262728
2、使用场景
Web应用、微服务 高并发I/O操作 网络客户端、爬虫 事件处理、消息队列消费
3、不适用于场景
CPU密集型计算 synchronized的场景
因为纯CPU计算,没有I/O操作 无法让渡载体线程(无法yield)
使用synchronized的后果
markdown
1. 载体线程被大量固定,无法处理其他虚拟线程
2. 虚拟线程的并发优势消失
3. 性能可能比传统线程池还差
123
尾巴
楼主【代码丰】全程手打创作不易 点点关注 收藏 点赞吧 谢谢朋友