前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >一文讲透虚拟线程!

一文讲透虚拟线程!

作者头像
架构狂人
发布2024-07-16 17:16:39
1130
发布2024-07-16 17:16:39
举报
文章被收录于专栏:架构狂人
什么是虚拟线程

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

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

“虚拟”线程,望文生义,它是“假”的,它不直接调度操作系统的线程,而是由 JVM 再提供一层线程的接口抽象,由普通线程调度,即一个普通的操作系统线程可以调度成千上万个虚拟线程。

虚拟线程比普通线程的消耗要小得多得多,在内存足够的情况下,我们甚至可以创建上百万的虚拟线程,这在之前(Java19 以前)是不可能的。

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

SpringBoot 使用虚拟线程

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

配置

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

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

代码语言:javascript
复制



/**
 * 配置是用于稍后测试,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 操作:

代码语言:javascript
复制


@Service
public class AsyncService {

    /**
     *
     * @param countDownLatch 用于测试
     */
    @Async
    public void doSomething(CountDownLatch countDownLatch) throws InterruptedException {
        Thread.sleep(50);
        countDownLatch.countDown();
    }
}

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

代码语言:javascript
复制


@Test
public void testAsync() throws InterruptedException {
    long start = System.currentTimeMillis();
    int n = 100000;
    CountDownLatch countDownLatch = new CountDownLatch(n);
    for (int i = 0; i < n; 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 操作:

代码语言:javascript
复制


@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 等等),使用虚拟线程的效果还是非常明显的。

最后:大部分的公司可能还在用 Java8,但是我想说的是,是时候升级了,跟上时代的脚步吧,朋友们!

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2024-07-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 顶尖架构师栈 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
腾讯云服务器利旧
云服务器(Cloud Virtual Machine,CVM)提供安全可靠的弹性计算服务。 您可以实时扩展或缩减计算资源,适应变化的业务需求,并只需按实际使用的资源计费。使用 CVM 可以极大降低您的软硬件采购成本,简化 IT 运维工作。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档