专栏名称: macrozheng
专注Java技术分享,解析优质开源项目。涵盖SpringBoot、SpringCloud、Docker、K8S等实用技术,作者Github开源项目mall(50K+Star)。
目录
相关文章推荐
手游那点事  ·  “上海圈vs广州圈vs北京圈,2025谁会更 ... ·  昨天  
内蒙古自治区文化和旅游厅  ·  跨新年 过大年 | ... ·  昨天  
内蒙古自治区文化和旅游厅  ·  跨新年 过大年 | ... ·  昨天  
手游那点事  ·  越烧越狠,2025游戏公司也开始“没钱”了? ·  3 天前  
手游那点事  ·  35岁熬进大厂但Leader比我小10岁,要 ... ·  2 天前  
手游那点事  ·  热议 | ... ·  4 天前  
51好读  ›  专栏  ›  macrozheng

SpringBoot + 虚拟线程,性能炸裂!

macrozheng  · 公众号  ·  · 2025-01-02 10:32

正文

Boot+Cloud项目学习: macrozheng.com

作者:哒哒哒打代码

来源:juejin.cn/post/7266745788536799247

什么是虚拟线程

虚拟线程是Java19开始增加的一个特性,和Golang的携程类似,一个其它语言早就提供的、且如此实用且好用的功能,作为一个Java开发者,早就已经望眼欲穿了。

虚拟线程和普通线程的区别

“虚拟”线程,望文生义,它是“假”的,它不直接调度操作系统的线程,而是由JVM再提供一层线程的接口抽象,由普通线程调度,即一个普通的操作系统线程可以调度成千上万个虚拟线程。虚拟线程比普通线程的消耗要小得多得多,在内存足够的情况下,我们甚至可以创建上百万的虚拟线程,这在之前(Java19以前)是不可能的。

其实如果有用过akka的朋友们会发现,其实两者很相似,只不过使用akka是应用程序来处理,而虚拟线程是JVM来处理,使用上更简洁且方便。

SpringBoot使用虚拟线程

下面我们会在SpringBoot中使用虚拟线程,将默认的异步线程池和http处理线程池替换为虚拟线程,然后对比虚拟线程和普通线程的性能差异,你会发现差别就像马车换高铁,不是一个时代的东西。

这或许是一个对你有用的开源项目 ,mall项目是一套基于 SpringBoot3 + Vue 的电商系统(Github标星60K),后端支持多模块和 2024最新微服务架构 ,采用Docker和K8S部署。包括前台商城项目和后台管理系统,能支持完整的订单流程!涵盖商品、订单、购物车、权限、优惠券、会员、支付等功能!

  • Boot项目: https://github.com/macrozheng/mall
  • Cloud项目: https://github.com/macrozheng/mall-swarm
  • 视频教程: https://www.macrozheng.com/video/

项目演示:

配置

首先我们使用的Java版本是 java-20.0.2-oracle ,SpringBoot版本是 3.1.2

要在SpringBoot中使用虚拟线程很简单,增加如下配置即可:

/**
 * 配置是用于稍后测试,spring.virtual-thread=true是使用虚拟线程,false时还是使用默认的普通线程
 */

@Configuration
@ConditionalOnProperty(prefix = "spring", name = "virtual-thread", havingValue = "true")
public class ThreadConfig {

    @Bean
    public AsyncTaskExecutor applicationTaskExecutor() {
        return new TaskExecutorAdapter(Executors.newVirtualThreadPerTaskExecutor());
    }

    @Bean
    public TomcatProtocolHandlerCustomizer> protocolHandlerCustomizer() {
        return protocolHandler -> {
            protocolHandler.setExecutor(Executors.newVirtualThreadPerTaskExecutor());
        };
    }
}

@Async性能对比

我们写一个异步service,里面睡眠50ms,模拟MySQL或Redis等IO操作:

@Service
public class AsyncService {

    /**
     * 
     * @param countDownLatch 用于测试
     */

    @Async
    public void doSomething(CountDownLatch countDownLatch) throws InterruptedException {
        Thread.sleep(50);
        countDownLatch.countDown();
    }
}

最后测试类,很简单,就是循环调用这个方法10万次,计算所有方法执行完成的消耗的时间:

@Test
    public void testAsync() throws InterruptedException {
        long start = System.currentTimeMillis();
        int n = 100000;
        CountDownLatch countDownLatch = new CountDownLatch(n);
        for (int i = 0; i             asyncService.doSomething(countDownLatch);
        }
        countDownLatch.await();
        long end = System.currentTimeMillis();
        System.out.println("耗时:" + (end - start) + "ms");
    }

普通线程耗时: 678秒 左右,超过10分钟了

虚拟线程耗时: 3.9秒!!

朋友们,接近 200倍 的性能差距!!

HTTP请求性能对比

让我们再看看http请求的对比,简单写个get请求,里面什么也不做,一样睡50ms,模拟IO操作:

@RequestMapping("/get")
    public Object get() throws Exception {
        Thread.sleep(50);
        return "ok";
    }

然后我们使用jmeter请求接口, 500个 并发线程,运行 1万次 ,看看效果如何:

普通线程:

可以看到最小用时 50ms ,这个没毛病,接口里面睡眠了50ms,但是不管是中位数还是 90/95/99线 都大于 150ms 了,这是因为系统线程是一个很昂贵的资源,SpringBoot中tomcat默认的最大连接数应该是200,在连接池的线程被耗尽后,这200个线程在那干等50ms结束,而剩下的请求也只能等待,无法进行其它的操作。下面再看下虚拟线程的表现:

虚拟线程耗时:

可以看到即使是最大耗时,也保持在100ms以下,即线程等待时间显著的减少,虚拟线程更好的利用了系统资源。

总结

从上面的性能对比来看,虚拟线程在性能方面有明显的优势,但是要注意的是,我们上面的测试都是让线程等待了50ms,这是模拟什么场景?没错,是 IO密集型 场景,即线程大部分时间是在等待IO,这样虚拟线程才可以发挥出它的优势,如果是 CPU密集型 场景,那么可能效果并不大。不过我们目前大部分的应用都是IO密集型应用较多,比如典型的 WEB应用 ,大量的时间在等待 网络IO (DB、缓存、HTTP等等),使用虚拟线程的效果还是非常明显的。







请到「今天看啥」查看全文