java 21虚拟线程vs传统线程 原理分析以及具体测试例子去分析性能提升

测试类探索 传统线程池和虚拟线程的性能对比

测试背景

模拟两个微服务的调用 去创建订单 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

尾巴

楼主【代码丰】全程手打创作不易 点点关注 收藏 点赞吧 谢谢朋友

相关推荐
用户0534369380731 小时前
langchainrust:Rust 版 LangChain 框架(LLM+Agent+RAG)
后端
fox_lht1 小时前
第十章 通用集合
开发语言·后端·算法·rust
悟空聊架构2 小时前
GStack的26种专家角色,真正实现一人成军!
后端
counting money2 小时前
Spring框架基础(依赖注入-半注解形式)
java·后端·spring
Code_Artist2 小时前
一天之内我让 AI 用 Netty 造了一个最小可用的 MVC 框架:体验一下造轮子的快感😅!
后端·netty·ai编程
也许明天y2 小时前
LangChain4j + Spring Boot 多智能体协调架构原理深度解析
spring boot·后端·agent
AI人工智能+电脑小能手2 小时前
【大白话说Java面试题】【Java基础篇】第20题:HashMap在计算index的时候,为什么要对数组长度做减1操作
java·开发语言·数据结构·后端·面试·哈希算法·hash-index
阿丰资源3 小时前
基于Spring Boot的新闻推荐系统(源码+数据库+文档)
数据库·spring boot·后端
Gopher_HBo4 小时前
Disruptor消费源码分析
后端