在 Spring 6 中使用虚拟线程
一、简介
在这个简短的教程中,我们将了解如何在 Spring Boot 应用程序中利用虚拟线程的强大功能。
虚拟线程是Java 19 的预览功能,这意味着它们将在未来 12 个月内包含在官方 JDK 版本中。Spring 6 版本最初由 Project Loom 引入,为开发人员提供了开始尝试这一出色功能的选项。
首先,我们将看到“平台线程”和“虚拟线程”之间的主要区别。接下来,我们将使用虚拟线程从头开始构建一个 Spring-Boot 应用程序。最后,我们将创建一个小型测试套件,以查看简单 Web 应用程序吞吐量的最终改进。
二、 虚拟线程与平台线程
主要区别在于虚拟线程在其操作周期中不依赖于操作系统线程:它们与硬件解耦,因此有了“虚拟”这个词。这种解耦是由 JVM 提供的抽象层实现的。
对于本教程来说,必须了解虚拟线程的运行成本远低于平台线程。它们消耗的分配内存量要少得多。这就是为什么可以创建数百万个虚拟线程而不会出现内存不足问题,而不是使用标准平台(或内核)线程创建几百个虚拟线程。
从理论上讲,这赋予了开发人员一种超能力:无需依赖异步代码即可管理高度可扩展的应用程序。
三、在Spring 6中使用虚拟线程
从 Spring Framework 6(和 Spring Boot 3)开始,虚拟线程功能正式公开,但虚拟线程是Java 19 的预览功能。这意味着我们需要告诉 JVM 我们要在应用程序中启用它们。由于我们使用 Maven 来构建应用程序,因此我们希望确保在 pom.xml 中包含以下代码:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>19</source>
<target>19</target>
<compilerArgs>
--enable-preview
</compilerArgs>
</configuration>
</plugin>
</plugins>
</build>复制
从 Java 的角度来看,要使用 Apache Tomcat 和虚拟线程,我们需要一个带有几个 bean 的简单配置类:
@EnableAsync
@Configuration
@ConditionalOnProperty(
value = "spring.thread-executor",
havingValue = "virtual"
)
public class ThreadConfig {
@Bean
public AsyncTaskExecutor applicationTaskExecutor() {
return new TaskExecutorAdapter(Executors.newVirtualThreadPerTaskExecutor());
}
@Bean
public TomcatProtocolHandlerCustomizer<?> protocolHandlerVirtualThreadExecutorCustomizer() {
return protocolHandler -> {
protocolHandler.setExecutor(Executors.newVirtualThreadPerTaskExecutor());
};
}
}复制
第一个 Spring Bean ApplicationTaskExecutor将取代标准的ApplicationTaskExecutor ,提供为每个任务启动新虚拟线程的Executor。第二个 bean,名为ProtocolHandlerVirtualThreadExecutorCustomizer,将以相同的方式 自定义标准TomcatProtocolHandler 。我们还添加了注释@ConditionalOnProperty,**以通过切换application.yaml文件中配置属性的值来按需启用虚拟线程:
spring:
thread-executor: virtual
//...复制
我们来测试一下Spring Boot应用程序是否使用虚拟线程来处理Web请求调用。为此,我们需要构建一个简单的控制器来返回所需的信息:
@RestController
@RequestMapping("/thread")
public class ThreadController {复制
@GetMapping("/name")
public String getThreadName() {
return Thread.currentThread().toString();
}
}复制
Thread对象的toString ()方法将返回我们需要的所有信息:线程 ID、线程名称、线程组和优先级。让我们通过一个curl请求来访问这个端点:
$ curl -s http://localhost:8080/thread/name
$ VirtualThread[#171]/runnable@ForkJoinPool-1-worker-4复制
正如我们所看到的,响应明确表示我们正在使用虚拟线程来处理此 Web 请求。换句话说,Thread.currentThread()调用返回虚拟线程类的实例。现在让我们通过简单但有效的负载测试来看看虚拟线程的有效性。
四、性能比较
对于此负载测试,我们将使用JMeter。这不是虚拟线程和标准线程之间的完整性能比较,而是我们可以使用不同参数构建其他测试的起点。
在这种特殊的场景中,我们将调用Rest Controller中的一个端点,该端点将简单地让执行休眠一秒钟,模拟复杂的异步任务:
@RestController
@RequestMapping("/load")
public class LoadTestController {
private static final Logger LOG = LoggerFactory.getLogger(LoadTestController.class);
@GetMapping
public void doSomething() throws InterruptedException {
LOG.info("hey, I'm doing something");
Thread.sleep(1000);
}
}复制
请记住,由于@ConditionalOnProperty 注释,我们只需更改 application.yaml 中变量的值即可在虚拟线程和标准线程之间切换。
JMeter 测试将仅包含一个线程组,模拟 1000 个并发用户访问/load 端点 100 秒:

在本例中,采用这一新功能所带来的性能提升是显而易见的。让我们比较不同实现的“响应时间图”。这是标准线程的响应图。我们可以看到,立即完成一次调用所需的时间达到 5000 毫秒:

发生这种情况是因为平台线程是一种有限的资源,当所有计划的和池化的线程都忙时,Spring 应用程序除了将请求搁置直到一个线程空闲之外别无选择。
让我们看看虚拟线程会发生什么:

正如我们所看到的,响应稳定在 1000 毫秒。虚拟线程在请求后立即创建和使用,因为从资源的角度来看它们非常便宜。在本例中,我们正在比较 spring 默认固定标准线程池(默认为 200)和 spring 默认无界虚拟线程池的使用情况。
这种性能提升之所以可能,是因为场景过于简单,并且没有考虑 Spring Boot 应用程序可以执行的全部操作。从底层操作系统基础设施中采用这种抽象可能是有好处的,但并非在所有情况下都是如此。
在 Spring 6 中使用虚拟线程的更多相关文章
- 在Tomcat中启用虚拟线程特性
前提 趁着国庆前后阅读了虚拟线程相关的源码,写了一篇<虚拟线程 - VirtualThread源码透视>,里面介绍了虚拟线程的实现原理和使用示例.需要准备做一下前期准备: 安装OpenJD ...
- Spring Boot中如何配置线程池拒绝策略,妥善处理好溢出的任务
通过之前三篇关于Spring Boot异步任务实现的博文,我们分别学会了用@Async创建异步任务.为异步任务配置线程池.使用多个线程池隔离不同的异步任务.今天这篇,我们继续对上面的知识进行完善和优化 ...
- 支持JDK19虚拟线程的web框架,之一:体验
欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 关于虚拟线程 随着JDK19 GA版本的发布,虚拟线程 ...
- 虚拟线程 - VirtualThread源码透视
前提 JDK19于2022-09-20发布GA版本,该版本提供了虚拟线程的预览功能.下载JDK19之后翻看了一下有关虚拟线程的一些源码,跟早些时候的Loom项目构建版本基本并没有很大出入,也跟第三方J ...
- 支持JDK19虚拟线程的web框架之四:看源码,了解quarkus如何支持虚拟线程
欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 前文链接 支持JDK19虚拟线程的web框架,之一:体 ...
- 支持JDK19虚拟线程的web框架,之五(终篇):兴风作浪的ThreadLocal
欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos <支持JDK19虚拟线程的web框架>系列 ...
- 【Spring】8、Spring框架中的单例Beans是线程安全的么
看到这样一个问题:spring框架中的单例Beans是线程安全的么? Spring框架并没有对单例bean进行任何多线程的封装处理.关于单例bean的线程安全和并发问题需要开发者自行去搞定.但实际上, ...
- 详解Spring Cloud中Hystrix 线程隔离导致ThreadLocal数据丢失
在Spring Cloud中我们用Hystrix来实现断路器,Zuul中默认是用信号量(Hystrix默认是线程)来进行隔离的,我们可以通过配置使用线程方式隔离. 在使用线程隔离的时候,有个问题是必须 ...
- Spring Boot中使用@Async的时候,千万别忘了线程池的配置!
上一篇我们介绍了如何使用@Async注解来创建异步任务,我可以用这种方法来实现一些并发操作,以加速任务的执行效率.但是,如果只是如前文那样直接简单的创建来使用,可能还是会碰到一些问题.存在有什么问题呢 ...
- Spring 中的 bean线程安全性分析
首先:Spring 中的 bean不是线程安全的 Spring容器中的Bean是否线程安全,容器本身并没有提供Bean的线程安全策略,因此可以说Spring容器中的Bean本身不具备线程安全的特性,但 ...
随机推荐
- Selenium - 元素定位(3) - CSS进阶
Selenium - 元素定位 CSS 定位进阶 元素示例 属性定位 # css 通过id属性定位 driver.find_element_by_css_selector("#kw" ...
- 【GiraKoo】Android Studio控制台乱码
[GiraKoo]Android Studio控制台乱码 启动Android Studio进行编译时,可能会遇到控制台出现异常的乱码. 本文介绍该情况的解决方案. ����: δ������쳣���� ...
- 代码随想录算法训练营Day24 回溯算法|216.组合总和III 17.电话号码的字母组合
代码随想录算法训练营 216.组合总和III 题目链接:216.组合总和III 找出所有相加之和为 n 的 k 个数的组合.组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字. 说 ...
- 从源码分析 Go 语言使用 cgo 导致的线程增长
TDengine Go 连接器 https://github.com/taosdata/driver-go 使用 cgo 调用 taos.so 中的 API,使用过程中发现线程数不断增长,本文从一个 ...
- 【Python入门教程】获取图片可视化精准定位(逆地理编码)
使用exifread库读取图片的经纬度信息(WGS84坐标) 使用高德开放API将经纬度转为高德底图经纬度,并输出具体位置 使用folium库,以高德底图为基准,在网页上可视化显示地图和兴趣点 一 ...
- 如何从零开始构建 API ?
假设你请承包商从零开始建造一座房子,你肯定期望他们交付最高质量的房子.他们必须通过检查.遵守安全规范并遵循项目中约定的要求.因为建房子可容不得走捷径.如果承包商经常走捷径,他们的声誉会受到影响,从而失 ...
- WFP必须掌握的技能之自定义控件——实战:自制上传文件显示进度按钮
自定义控件在WPF开发中是很常见的,有时候某些控件需要契合业务或者美化统一样式,这时候就需要对控件做出一些改造. 目录 按钮设置圆角 按钮上传文件相关定义 测试代码 话不多说直接看效果 默认效果: 上 ...
- 解密Prompt系列8. 无需训练让LLM支持超长输入:知识库 & unlimiformer & PCW & NBCE
这一章我们聊聊有哪些方案可以不用微调直接让大模型支持超长文本输入,注意这里主要针对无限输入场景.之前在BERT系列中我们就介绍过稀疏注意力和片段递归的一些长文本建模方案长文本建模 BigBird &a ...
- Unity 制作KinematicCharacterController
本篇博客为游戏开发记录,博主只是想自己做个移动组件给自己做游戏用,此间产生的一些经验也做一个分享. 简介 为了在3D世界中自由的控制我们的角色,引擎一般会提供一些基础的移动组件,上层用户做提供一些每帧 ...
- 行行AI人才直播第5期:系列课-AI理解及ChatGPT从基础到高级应用
当前,人工智能是全世界研究的重点对象,也是人们茶余饭后讨论的经典话题.自从 OpenAI 发布 ChatGPT-4 之后,似乎无论是在工作.娱乐.甚至是日常生活中,我们都能感受到AI带来的便利和改变. ...