当前位置: 欣欣网 > 码农

SpringBoot + 虚拟线程,鸟枪换大炮

2024-06-03码农

架构师(JiaGouX)

我们都是架构师!
架构未来,你来不来?

什么是虚拟线程

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

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

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

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

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

SpringBoot 使用虚拟线程

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

配置

首先我们使用的 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 classThreadConfig {
@Bean
public AsyncTaskExecutor applicationTaskExecutor() {
returnnewTaskExecutorAdapter(Executors.newVirtualThreadPerTaskExecutor());
}
@Bean
public TomcatProtocolHandlerCustomizer<?>protocolHandlerCustomizer() {
return protocolHandler -> {
protocolHandler.setExecutor(Executors.newVirtualThreadPerTaskExecutor());
};
}
}


@Async 性能对比

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

@Service
public classAsyncService {
/**
*
* @paramcountDownLatch 用于测试
*/

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

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

@Test
publicvoidtestAsync() throws InterruptedException {
long start = System.currentTimeMillis();
int n =100000;
CountDownLatch countDownLatch =newCountDownLatch(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 操作:

@RequestMapping("/get")
publicObjectget() 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,但是我想说的是,是时候升级了,跟上时代的脚步吧,朋友们!

如喜欢本文,请点击右上角,把文章分享到朋友圈
如有想了解学习的技术点,请留言给若飞安排分享

因公众号更改推送规则,请点「在看」并加「星标」 第一时间获取精彩技术分享

·END·

相关阅读:

作者:哒哒哒打代码

来源:https://juejin.cn/post/7266745788536799247

版权申明:内容来源网络,仅供学习研究,版权归原创者所有。如有侵权烦请告知,我们会立即删除并表示歉意。谢谢!

架构师

我们都是架构师!

关注 架构师(JiaGouX),添加「星标」

获取每天技术干货,一起成为牛逼架构师

技术群请 加若飞: 1321113940 进架构师群

投稿、合作、版权等邮箱: [email protected]