首页 > 其他分享 >在 Spring 6 中使用虚拟线程

在 Spring 6 中使用虚拟线程

时间:2023-08-27 19:11:05浏览次数:43  
标签:Spring 应用程序 public 虚拟 线程 我们

一、简介

在这个简短的教程中,我们将了解如何在 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 秒:

JMeter 线程组

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

标准螺纹性能

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

让我们看看虚拟线程会发生什么:

虚拟线程图

正如我们所看到的,响应稳定在 1000 毫秒。虚拟线程在请求后立即创建和使用,因为从资源的角度来看它们非常便宜。在本例中,我们正在比较 spring 默认固定标准线程池(默认为 200)和 spring 默认无界虚拟线程池的使用情况。

这种性能提升之所以可能,是因为场景过于简单,并且没有考虑 Spring Boot 应用程序可以执行的全部操作。从底层操作系统基础设施中采用这种抽象可能是有好处的,但并非在所有情况下都是如此。

标签:Spring,应用程序,public,虚拟,线程,我们
From: https://www.cnblogs.com/w1570631036/p/17660683.html

相关文章

  • SpringBoot启动时:Process finished with exit code 0解决办法
    Processfinishedwithexitcode0并不是报错了,这个表示程序正常执行完毕退出了。这就表示项目启动成功后了,此时运行,最后运行完毕自动退出。但我们是需要访问路径的,所以需要引入webjar包<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot......
  • 超详细的彻底卸载VMware虚拟机方法
    一、在卸载VMware虚拟机之前,要先把与VMware相关的服务和进程终止1、在windows中按下【Windows键】,搜索【服务】设置,然后打开;2、找到以VM打头命名的服务,然后右键停止这些服务;3、在windows中使用【Crtl+Shift+Esc】打开任务管理器,并找到以VM打头命名的进程,然后......
  • Springboot和SpringMVC
    SpringBoot是一个用于创建独立的、基于Spring框架的Java应用程序的开源框架。它简化了以往需要在Spring应用中进行繁琐配置的过程,提供了开箱即用的配置和约定,使得开发者能够更集中精力于业务逻辑的实现。SpringBoot提供了许多优秀的特性,其中包括:1.自动配置(Auto-config......
  • Springboot是什么
    SpringBoot是一个用于简化Spring应用程序开发的框架。它是基于Spring框架的一种约定优于配置的方式,旨在帮助开发者更快速、更容易地构建独立的、生产级的Spring应用程序。SpringBoot提供了一套默认配置,用于自动配置各种常见的第三方库和框架,减少了开发者的配置工作。同......
  • Linux 多线程基础
    @TOC前言一、多线程基础函数1.pthread_create创建新的线程。#include<pthread.h> intpthread_create(pthread_t*thread,constpthread_attr_t*attr, void*(*start_routine)(void*),void*arg);参数说明:thread:用于存储新线程的ID。attr:线程属......
  • 多线程基础
    进程在计算机中,我们把一个任务称为一个进程,浏览器就是一个进程,视频播放器是另一个进程。某些进程内部还需要同时执行多个子任务。例如,我们在使用Word时,Word可以让我们一边打字,一边进行拼写检查,我们把子任务称为线程。进程和线程的关系:一个进程可以包含一个或多个线程,但至少会有一个......
  • 4、Spring配置
    4、Spring配置4.1、别名<!--别名如果我们使用了别名,那么我们既可以使用原来的名字=,也可以使用别名来获取对象--><aliasname="user"alias="userNew"></alias>4.2、Bean的配置<!--idBean的唯一标识符相当于我们的对象名classBean所对应的全限定名name也是......
  • 9、使用Java的方式配置Spring
    9、使用Java的方式配置Spring我们现在完全不需要Spring的xml配置文件了,全权交给Java来做Javaconfig是Spring的一个子项目,在Spring4之后他成为了一个核心功能使用纯Java的方式来配置Spring需要有一个配置类packagecom.an.Config;importcom.an.pojo.User;importorg.springfra......
  • spring中的ApplicationEventPublisher的使用
    spring中的ApplicationEventPublisher是spring对发布订阅模式的一种支持,要了解它的作用和使用需要先了解下观察者模式和发布订阅模式。目录一、观察者模式二、发布订阅模式三、spring中的ApplicationEventPublisher一、观察者模式观察者模式中涉及观察者,被观察者两种角色,其......
  • 线程安全的集合
    目录多线程环境下使用Dictionary产生的问题何时使用线程安全集合解决办法相关参考.NET中的Dictionary是非线程安全的,在多线程环境中可能会导致CPU使用率为100%。多线程环境下使用Dictionary产生的问题关于C#中Dictionary多线程情况下CPU100%问题的详细分析关于C#的Dictionar......