您当前的位置:首页 > 电脑百科 > 程序开发 > 编程百科

虚拟线程原理及性能分析

时间:2023-11-07 14:15:48  来源:微信公众号  作者:得物技术

一、背景

JDK21 在 9 月 19 号正式发布,带来了较多亮点,其中虚拟线程备受瞩目,毫不夸张的说,它改变了高吞吐代码的编写方式,只需要小小的变动就可以让目前的 IO 密集型程序的吞吐量得到提升,写出高吞吐量的代码不再困难。

本文将详细介绍虚拟线程的使用场景,实现原理以及在 IO 密集型服务下的性能压测效果。

二、为了提升吞吐性能,我们所做的优化

在讲虚拟线程之前,我们先聊聊为了提高吞吐性能,我们所做的一些优化方案。

串行模式

在当前的微服务架构下,处理一次用户/上游的请求,往往需要多次调用下游服务、数据库、文件系统等,再将所有请求的数据进行处理最终的结果返回给上游。

虚拟线程原理及性能分析图片

虚拟线程原理及性能分析图片

在这种模式下,使用串行模式去查询数据库,下游 Dubbo/Http 接口,文件系统完成一次请求,接口整体的耗时等于各个下游的返回时间之和,这种写法虽然简单,但是接口耗时长、性能差,无法满足 C 端高 QPS 场景下的性能要求。

线程池+Future异步调用

为了解决串行调用的低性能问题,我们会考虑使用并行异步调用的方式,最简单的方式便是使用线程池 +Future 去并行调用。

虚拟线程原理及性能分析图片

典型代码如下:

虚拟线程原理及性能分析图片

这种方式虽然解决了大部分场景下的串行调用低性能问题,但是也存在着严重的弊端,由于存在 Future 的前后依赖关系,当使用场景存在大量的前后依赖时,会使得线程资源和 CPU 大量浪费在阻塞等待上,导致资源利用率低。

线程池+CompletableFuture异步调用

为了降低 CPU 的阻塞等待时间和提升资源的利用率,我们会使用CompletableFuture对调用流程进行编排,降低依赖之间的阻塞。

CompletableFuture 是由 JAVA8 引入的,在 Java8 之前一般通过 Future 实现异步。Future 用于表示异步计算的结果,如果存在流程之间的依赖关系,那么只能通过阻塞或者轮询的方式获取结果,同时原生的 Future 不支持设置回调方法,Java8 之前若要设置回调可以使用 Guava 的 ListenableFuture,回调的引入又会导致回调地狱,代码基本不具备可读性。

而 CompletableFuture 是对 Future 的扩展,原生支持通过设置回调的方式处理计算结果,同时也支持组合编排操作,一定程度解决了回调地狱的问题。

使用 CompletableFuture 的实现方式如下:

虚拟线程原理及性能分析图片

CompletableFuture 虽然一定程度上面缓解了 CPU 资源大量浪费在阻塞等待上的问题,但是只是缓解,核心的问题始终没有解决。这两个问题导致 CPU 无法充分被利用,系统吞吐量容易达到瓶颈。

线程资源浪费瓶颈始终在 IO 等待上,导致 CPU 资源利用率较低。目前大部分服务是 IO 密集型服务,一次请求的处理耗时大部分都消耗在等待下游 RPC,数据库查询的 IO 等待中,此时线程仍然只能阻塞等待结果返回,导致 CPU 的利用率很低。

线程数量存在限制, 为了增加并发度,我们会给线程池配置更大的线程数,但是线程的数量是有限制的,Java 的线程模型是 1:1 映射平台线程的,导致 Java 线程创建的成本很高,不能无限增加。同时随着 CPU 调度线程数的增加,会导致更严重的资源争用,宝贵的 CPU 资源被损耗在上下文切换上。

三、一请求一线程的模型

在给出最终解决方案之前,我们先聊一聊 Web 应用中常见的一请求一线程的模型。

在 Web 中我们最常见的请求模型就是使用一请求一线程的模型,每个请求都由单独的线程处理。此模型易于理解和实现,对编码的可读性,Debug 都非常友好,但是,它有一些缺点。当线程执行阻塞操作(如连接到数据库或进行网络调用)时,线程会被阻塞,直到操作完成,这意味着线程在此期间将无法处理任何其他请求。

虚拟线程原理及性能分析图片

当遇到大促或突发流量等场景导致服务承受的请求数增大时,为了保证每个请求在尽可能短的时间内返回,减少等待时间,我们经常会采用以下方案:

  • 扩大服务最大线程数,简单有效,由于存在下列问题,导致平台线程有最大数量限制,不能大量扩充。

系统资源有限导致系统线程总量有限,进而导致与系统线程一一对应的平台线程有限。

平台线程的调度依赖于系统的线程调度程序,当平台线程创建过多,会消耗大量资源用于处理线程上下文切换。

每个平台线程都会开辟一块大小约 1m 私有的栈空间,大量平台线程会占据大量内存

虚拟线程原理及性能分析图片

  • 垂直扩展,升级机器配置,水平扩展,增加服务节点,也就是俗称的升配扩容大法,效果好,也是最常见的方案,缺点是会增加成本,同时有些场景下扩容并不能 100% 解决问题。
  • 采用异步/响应式编程方案,例如 RPC NIO 异步调用,WebFlux,Rx-Java 等非阻塞的基于 Ractor 模型的框架,使用事件驱动使得少量线程即可实现高吞吐的请求处理,拥有较好的性能与优秀的资源利用,缺点是学习成本较高兼容性问题较大,编码风格与目前的一请求一线程的模型差异较大,理解难度大,同时对于代码的调试比较困难。

那么有没有一种方法可以易于编写,方便迁移,符合日常编码习惯,同时性能很不错,CPU 资源利用率较高的方案呢?

JDK21 中的虚拟线程可能给出了答案, JDK 提供了与 Thread 完全一致的抽象 Virtual Thread 来应对这种经常阻塞的情况,阻塞仍然是会阻塞,但是换了阻塞的对象,由昂贵的平台线程阻塞改为了成本很低的虚拟线程的阻塞,当代码调用到阻塞 API 例如 IO,同步,Sleep 等操作时,JVM 会自动把 Virtual Thread 从平台线程上卸载,平台线程就会去处理下一个虚拟线程,通过这种方式,提升了平台线程的利用率,让平台线程不再阻塞在等待上,从底层实现了少量平台线程就可以处理大量请求,提高了服务吞吐和 CPU 的利用率。

四、虚拟线程

线程术语定义

操作系统线程(OS Thread):由操作系统管理,是操作系统调度的基本单位。

平台线程(Platform Thread):Java.Lang.Thread 类的每个实例,都是一个平台线程,是 Java 对操作系统线程的包装,与操作系统是 1:1 映射。

虚拟线程(Virtual Thread):一种轻量级,由 JVM 管理的线程。对应的实例 java.lang.VirtualThread 这个类。

载体线程(Carrier Thread):指真正负责执行虚拟线程中任务的平台线程。一个虚拟线程装载到一个平台线程之后,那么这个平台线程就被称为虚拟线程的载体线程。

虚拟线程定义

JDK 中 java.lang.Thread 的每个实例都是一个平台线程。平台线程在底层操作系统线程上运行 Java 代码,并在代码的整个生命周期内独占操作系统线程,平台线程实例本质是由系统内核的线程调度程序进行调度,并且平台线程的数量受限于操作系统线程的数量。

而虚拟线程(Virtual Thread)它不与特定的操作系统线程相绑定。它在平台线程上运行 Java 代码,但在代码的整个生命周期内不独占平台线程。这意味着许多虚拟线程可以在同一个平台线程上运行他们的 Java 代码,共享同一个平台线程。同时虚拟线程的成本很低,虚拟线程的数量可以比平台线程的数量大得多。

虚拟线程原理及性能分析图片

虚拟线程创建

方法一:直接创建虚拟线程

Thread vt = Thread.startVirtualThread(() -> {
    System.out.println("hello wolrd virtual thread");
});

方法二:创建虚拟线程但不自动运行,手动调用start()开始运行

Thread.ofVirtual().unstarted(() -> {
    System.out.println("hello wolrd virtual thread");
});
vt.start();

方法三:通过虚拟线程的 ThreadFactory 创建虚拟线程

ThreadFactory tf = Thread.ofVirtual().factory();
Thread vt = tf.newThread(() -> {
    System.out.println("Start virtual thread...");
    Thread.sleep(1000);
    System.out.println("End virtual thread. ");
});
vt.start();


ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
executor.submit(() -> {
    System.out.println("Start virtual thread...");
    Thread.sleep(1000);
    System.out.println("End virtual thread.");
    return true;
});

虚拟线程实现原理

虚拟线程是由 Java 虚拟机调度,而不是操作系统。虚拟线程占用空间小,同时使用轻量级的任务队列来调度虚拟线程,避免了线程间基于内核的上下文切换开销,因此可以极大量地创建和使用。

简单来看,虚拟线程实现如下:virtual thread =continuation+scheduler+runnable

虚拟线程会把任务(java.lang.Runnable实例)包装到一个 Continuation 实例中:

  • 当任务需要阻塞挂起的时候,会调用 Continuation 的 yield 操作进行阻塞,虚拟线程会从平台线程卸载。
  • 当任务解除阻塞继续执行的时候,调用 Continuation.run 会从阻塞点继续执行。

Scheduler 也就是执行器,由它将任务提交到具体的载体线程池中执行。

  • 它是 java.util.concurrent.Executor 的子类。
  • 虚拟线程框架提供了一个默认的 FIFO 的 ForkJoinPool 用于执行虚拟线程任务。

Runnable 则是真正的任务包装器,由 Scheduler 负责提交到载体线程池中执行。

JVM 把虚拟线程分配给平台线程的操作称为 mount(挂载),取消分配平台线程的操作称为 unmount(卸载):

mount 操作:虚拟线程挂载到平台线程,虚拟线程中包装的 Continuation 堆栈帧数据会被拷贝到平台线程的线程栈,这是一个从堆复制到栈的过程。

unmount 操作:虚拟线程从平台线程卸载,此时虚拟线程的任务还没有执行完成,所以虚拟线程中包装的 Continuation 栈数据帧会会留在堆内存中。

从 Java 代码的角度来看,其实是看不到虚拟线程及载体线程共享操作系统线程的,会认为虚拟线程及其载体都在同一个线程上运行,因此,在同一虚拟线程上多次调用的代码可能会在每次调用时挂载的载体线程都不一样。JDK 中使用了 FIFO 模式的 ForkJoinPool 作为虚拟线程的调度器,从这个调度器看虚拟线程任务的执行流程大致如下:

  • 调度器(线程池)中的平台线程等待处理任务。

虚拟线程原理及性能分析图片

  • 一个虚拟线程被分配平台线程,该平台线程作为载体线程执行虚拟线程中的任务。

虚拟线程原理及性能分析图片

  • 虚拟线程运行其 Continuation,Mount(挂载)平台线程后,最终执行 Runnable 包装的用户实际任务。

虚拟线程原理及性能分析图片

  • 虚拟线程任务执行完成,标记 Continuation 终结,标记虚拟线程为终结状态,清空上下文,等待 GC 回收,解除挂载载体线程会返还到调度器(线程池)中等待处理下一个任务。

虚拟线程原理及性能分析图片

上面是没有阻塞场景的虚拟线程任务执行情况,如果遇到了阻塞(例如 Lock 等)场景,会触发 Continuation 的 yield 操作让出控制权,等待虚拟线程重新分配载体线程并且执行,具体见下面的代码:

ReentrantLock lock = new ReentrantLock();
        Thread.startVirtualThread(() -> {
            lock.lock();    
        });
        // 确保锁已经被上面的虚拟线程持有
        Thread.sleep(1000);  
        Thread.startVirtualThread(() -> {
            System.out.println("first");
            会触发Continuation的yield操作
            lock.lock(); 
            try {
                System.out.println("second");
            } finally {
                lock.unlock();
            }
            System.out.println("third");
        });
        Thread.sleep(Long.MAX_VALUE);
    }
  • 虚拟线程中任务执行时候调用 Continuation#run() 先执行了部分任务代码,然后尝试获取锁,该操作是阻塞操作会导致 Continuation 的 yield 操作让出控制权,如果 yield 操作成功,会从载体线程 unmount,载体线程栈数据会移动到 Continuation 栈的数据帧中,保存在堆内存中,虚拟线程任务完成,此时虚拟线程和 Continuation 还没有终结和释放,载体线程被释放到执行器中等待新的任务;如果 Continuation 的 yield 操作失败,则会对载体线程进行 Park 调用,阻塞在载体线程上,此时虚拟线程和载体线程同时会被阻塞,本地方法,Synchronized 修饰的同步方法都会导致 yield 失败。

虚拟线程原理及性能分析图片

  • 当锁持有者释放锁之后,会唤醒虚拟线程获取锁,获取锁成功后,虚拟线程会重新进行 mount,让虚拟线程任务再次执行,此时有可能是分配到另一个载体线程中执行,Continuation 栈会的数据帧会被恢复到载体线程栈中,然后再次调用Continuation#run() 恢复任务执行。

虚拟线程原理及性能分析图片

  • 虚拟线程任务执行完成,标记 Continuation 终结,标记虚拟线程为终结状态,清空上下文变量,解除载体线程的挂载载体线程返还到调度器(线程池)中作为平台线程等待处理下一个任务。

Continuation 组件十分重要,它既是用户真实任务的包装器,同时提供了虚拟线程任务暂停/继续的能力,以及虚拟线程与平台线程数据转移功能,当任务需要阻塞挂起的时候,调用 Continuation 的 yield 操作进行阻塞。当任务需要解除阻塞继续执行的时候,则调用 Continuation 的 run 恢复执行。

通过下面的代码可以看出 Continuation 的神奇之处,通过在编译参数加上--add-exports java.base/jdk.internal.vm=ALL-UNNAMED 可以在本地运行。

ContinuationScope scope = new ContinuationScope("scope");
Continuation continuation = new Continuation(scope, () -> {
    System.out.println("before yield开始");
    Continuation.yield(scope);
    System.out.println("after yield 结束");
});
System.out.println("1 run");
// 第一次执行Continuation.run
continuation.run();
System.out.println("2 run");
// 第二次执行Continuation.run
continuation.run();
System.out.println("Done");

虚拟线程原理及性能分析图片

通过上述案例可以看出,Continuation 实例进行 yield 调用后,再次调用其 run 方法就可以从 yield 的调用之处继续往下执行,从而实现了程序的中断和恢复。

虚拟线程内存占用评估

单个平台线程的资源占用:

  • 根据 JVM 规范,预留 1 MB 线程栈空间。
  • 平台线程实例,会占据 2000+ byte 数据。

单个虚拟线程的资源占用:

  • Continuation 栈会占用数百 byte 到数百 KB 内存空间,是作为堆栈块对象存储在 Java 堆中。
  • 虚拟线程实例会占据 200 - 240 byte 数据。

从对比结果来看,理论上单个平台线程占用的内存空间至少是 KB 级别的,而单个虚拟线程实例占用的内存空间是 byte 级别,两者的内存占用差距较大,这也是虚拟线程可以大批量创建的原因。

下面通过一段程序去测试平台线程和虚拟线程的内存占用:

private static final int COUNT = 4000;


/**
 *  -XX:NativeMemoryTracking=detail
 *
 * @param args args
 */
public static void main(String[] args) throws Exception {
    for (int i = 0; i < COUNT; i++) {
        new Thread(() -> {
            try {
                Thread.sleep(Long.MAX_VALUE);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, String.valueOf(i)).start();
    }
    Thread.sleep(Long.MAX_VALUE);
}

上面的程序运行后启动 4000 平台线程,通过 -XX:NativeMemoryTracking=detail 参数和 JCMD 命令查看所有线程占据的内存空间如下:

虚拟线程原理及性能分析图片

内存占用大部分来自创建的平台线程,总线程栈空间占用约为 8096 MB,两者加起来占据总使用内存(8403MB)的 96% 以上。

用类似的方式编写运行虚拟线程的程序:

private static final int COUNT = 4000;


/**
 * -XX:NativeMemoryTracking=detail
 *
 * @param args args
 */
public static void main(String[] args) throws Exception {
    for (int i = 0; i < COUNT; i++) {
        Thread.startVirtualThread(() -> {
            try {
                Thread.sleep(Long.MAX_VALUE);
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
    }
    Thread.sleep(Long.MAX_VALUE);
}

上面的程序运行后启动 4000 虚拟线程:

虚拟线程原理及性能分析图片

堆内存的实际占用量和总内存的实际占用量都不超过 300 MB,可以证明虚拟线程在大量创建的前提下也不会去占用过多的内存,且虚拟线程的堆栈是作为堆栈块对象存储在 Java 的堆中的,可以被 GC 回收,又降低了虚拟线程的占用。

虚拟线程的局限及使用建议

  • 虚拟线程存在 native 方法或者外部方法 (Foreign Function & Memory API,jep 424 ) 调用不能进行 yield 操作,此时载体线程会被阻塞。
  • 当运行在 synchronized 修饰的代码块或者方法时,不能进行 yield 操作,此时载体线程会被阻塞,推荐使用 ReentrantLock。
  • ThreadLocal 相关问题,目前虚拟线程仍然是支持 ThreadLocal 的,但是由于虚拟线程的数量非常多,会导致 Threadlocal 中存的线程变量非常多,需要频繁 GC 去清理,对性能会有影响,官方建议尽量少使用 ThreadLocal,同时不要在虚拟线程的 ThreadLocal 中放大对象,目前官方是想通过 ScopedLocal 去替换掉 ThreadLocal,但是在 21 版本还没有正式发布,这个可能是大规模使用虚拟线程的一大难题。
  • 无需池化虚拟线程 虚拟线程占用的资源很少,因此可以大量地创建而无须考虑池化,它不需要跟平台线程池一样,平台线程的创建成本比较昂贵,所以通常选择去池化,去做共享,但是池化操作本身会引入额外开销,对于虚拟线程池化反而是得不偿失,使用虚拟线程我们抛弃池化的思维,用时创建,用完就扔。

虚拟线程适用场景

  • 大量的 IO 阻塞等待任务,例如下游 RPC 调用,DB 查询等。
  • 大批量的处理时间较短的计算任务。
  • Thread-per-request (一请求一线程)风格的应用程序,例如主流的 Tomcat 线程模型或者基于类似线程模型实现的 SpringMVC 框架 ,这些应用只需要小小的改动就可以带来巨大的吞吐提升。

五、虚拟线程压测性能分析

在下面的测试中,我们将模拟最常使用的场景-使用 Web 容器去处理 Http 请求。

场景一:在 Spring Boot 中使用内嵌的 Tomcat 去处理 Http 请求,使用默认的平台线程池作为 Tomcat 的请求处理线程池。

场景二:使用 Spring -WebFlux 创建基于事件循环模型的应用程序,进行响应式请求处理。

场景三:在 Spring Boot 中使用内嵌的 Tomcat 去处理 Http 请求,使用虚拟线程池作为 Tomcat 的请求处理线程池 (Tomcat已支持虚拟线程)。

测试流程

  • Jmeter 开启 500 个线程去并行发起请求。每个线程将等待请求响应后再发起下一次请求,单次请求超时时间为 10s,测试时间持续 60s。
  • 测试的 Web Server 将接受 Jmeter 的请求,并调用慢速服务器获取响应并返回。
  • 慢速服务器以随机超时响应。最大响应时间为 1000ms。平均响应时间为 500ms。

虚拟线程原理及性能分析图片

衡量指标

吞吐量和平均响应时间,吞吐量越高,平均响应时间越低,性能就越好。

Tomcat+普通线程池

默认情况下,Tomcat 使用一请求一线程模型处理请求,当 Tomcat 收到请求时,会从线程池中取一个线程去处理请求,该分配的线程将一直保持占用状态,直到请求结束才会释放。当线程池中没有线程时,请求会一直阻塞在队列中,直到有请求结束释放线程。默认队列长度为 Integer.MAX。默认线程池默认情况下,线程池最多包含 200 个线程。这基本上意味着单个时间点最多处理 200 个请求。对于每个请求服务都会以阻塞的方式调用平均 RT500ms 的慢速服务器。因此,可以预期每秒 400 个请求的吞吐量,最终压测结果非常接近预期值,为 388 req/sec。

虚拟线程原理及性能分析

增加线程池

生产环境为了吞吐考虑,一般不会使用默认值,会把线程池增大到 server.tomcat.threads.max=500+,调整到 500+ 之后的压测结果如下:

虚拟线程原理及性能分析

可以看出最终的吞吐量和线程数量呈比例上升,同时由于线程数的增加,请求等待减少,平均 RT 趋向于慢速服务器的响应平均 RT。

但是需要注意的是,平台线程的创建受到内存和 Java 线程映射模型的限制,不能无限扩展,同时大量线程会导致 CPU 资源大量消耗在上下文切换时,整体性能反而降低。

WebFlux

WebFlux 跟传统的 Tomcat 线程模型不一样,他不会为每个请求分配一个专用线程,而是使用事件循环模型通过非阻塞 I/O 操作同时处理多个请求,这使得它能够用有限的线程数量处理大量的并发请求。

在压测的场景下,使用 WebClient 来进行一个非阻塞的 Http 调用慢速处理器,并使用 RouterFunction 来做请求映射和处理。

@Bean
public WebClient slowServerClient() {
    return WebClient.builder()
            .baseUrl("http://127.0.0.1:8000")
            .build();
}


@Bean
public RouterFunction<ServerResponse> routes(WebClient slowServerClient) {
    return route(GET("/"), (ServerRequest req) -> ok()
            .body(
                    slowServerClient
                            .get()
                            .exchangeToFlux(resp -> resp.bodyToFlux(Object.class)),
                    Object.class
            ));
}

WebFlux 压测结果如下:

虚拟线程原理及性能分析图片

可以看到,WebFlux 的请求完全没有阻塞,仅用了 25 个线程就达到了 964 req/sec 的吞吐。

Tomcat+虚拟线程池

与平台线程相比,虚拟线程的内存占用量要低得多,运行程序大量的创建虚拟线程,而不会耗尽系统资源;同时当遇到 Thread.sleep(),CompletableFuture.await(),等待 I/O,获取锁时,虚拟线程会自动卸载,JVM 可以自动切换到另外的等待就绪的虚拟线程,提升单个平台线程的利用率,保证平台线程不会浪费在无意义的阻塞等待上。

要想使用虚拟线程,需要先在启动参数中加上 --enable-preview,同时 Tomcat 在 10 版本已支持虚拟线程,我们只需要替换 Tomcat 的平台线程池为虚拟线程池即可。

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




private final RestTemplate restTemplate;


@GetMApping
public ResponseEntity<Object> callSlowServer(){
    return restTemplate.getForEntity("http://127.0.0.1:8000", Object.class);
}

最终压测结果如下:

虚拟线程原理及性能分析图片

可以看到虚拟线程的压测结果实际上与 WebFlux 的情况相同,但我们根本没有使用任何复杂的响应式编程技术。同时对慢速服务器的调用,也使用常规的阻塞  RestTemplate。我们所做的只是用虚拟线程执行器替换线程池就达到更复杂的 Webflux 写法相同的效果。

总的压测结果如下:

虚拟线程原理及性能分析

通过以上压测结果,我们可以得出以下结论:

  • 传统的线程池模式效果差强人意,可以通过提高线程数量可以提升吞吐,但是需要考虑到系统容量和资源限制,但是对于大部分场景来说使用线程池去处理阻塞操作仍然是主流且不错的选择。
  • WebFlux 的效果非常好,但是考虑到需要完全按照响应式风格进行开发,成本及难度较大,同时 WebFlux 与现有的一些主流框架存在一些兼容问题,例如 MySQL 官方 IO 库不支持 NIO、Threadlocal 兼容问题等等。现有应用的迁移基本要重写所有代码,改动量和风险都不可控。
  • 虚拟线程的效果非常好,最大的优势就是我们没有修改代码或采用任何反应式技术,唯一更改是将线程池替换为虚拟线程。虽然改动较小,但与使用线程池相比,性能结果得到了显著改善。

基于上述的压测结果,可以较为乐观的认为虚拟线程会颠覆我们目前的服务和框架中的请求处理方法。

六、总结

过去很长时间,在编写服务端应用时,我们对于每个请求,都使用独占的线程来处理,请求之间是相互独立的,这就是 一请求一线程的模型这种方式易于理解和编程实现,也易于调试和性能调优。

然而,一请求一线程风格并不能简单地使用平台线程来实现,因为平台线程是操作系统中线程的封装。操作系统的线程会申请成本较高,存在数量上限。对于一个要并发处理海量请求的服务器端应用来说,对每个请求都创建一个平台线程是不现实的。在这种前提下,涌现出一批非阻塞 I/O 和异步编程框架,如 WebFlux ,RX-Java。当某个请求在等待 I/O 操作时,它会暂时让出线程,并在 I/O 操作完成之后继续执行。通过这种方式,可以用少量线程同时处理大量的请求。这些框架可以提升系统的吞吐量,但是要求开发人员必须熟悉所使用的底层框架,并按照响应式的风格来编写代码,响应式框架的调试困难,学习成本,兼容问题使得大部分人望而却步 。

在使用虚拟线程之后,一切都将改变,开发人员可以使用目前最习惯舒服的方式来编写代码,高性能和高吞吐由虚拟线程自动帮你完成,这极大地降低了编写高并发服务应用的难度。

参考文档

  1. https://openjdk.org/jeps/444
  2. https://zhuanlan.zhihu.com/p/514719325
  3. https://www.vlts.cn/post/virtual-thread-source-code#%E5%89%8D%E6%8F%90
  4. https://zhuanlan.zhihu.com/p/499342616


Tags:虚拟线程   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
Spring Boot虚拟线程的性能还不如Webflux?
早上看到一篇关于Spring Boot虚拟线程和Webflux性能对比的文章,觉得还不错。内容较长,抓重点给大家介绍一下这篇文章的核心内容,方便大家快速阅读。测试场景作者采用了一个尽可...【详细内容】
2024-01-10  Search: 虚拟线程  点击:(115)  评论:(0)  加入收藏
Java 21 神仙特性:虚拟线程使用指南
虚拟线程是由 Java 21 版本中实现的一种轻量级线程。它由 JVM 进行创建以及管理。虚拟线程和传统线程(我们称之为平台线程)之间的主要区别在于,我们可以轻松地在一个 Java 程序...【详细内容】
2023-12-28  Search: 虚拟线程  点击:(107)  评论:(0)  加入收藏
响应式编程又变天了?看JDK21虚拟线程如何颠覆!
命令式风格编程一直深受开发者喜爱,如 if-then-else、while 循环、函数和代码块等结构使代码易理解、调试,异常易追踪。然而,像所有好的东西一样,通常也有问题。这种编程风格导...【详细内容】
2023-12-28  Search: 虚拟线程  点击:(100)  评论:(0)  加入收藏
三分钟理解 Java 虚拟线程
虚拟线程是 Java 语言中实现的一种轻量级线程,在 Java 项目中可以减少编写、维护和调试高吞吐量并发应用程序的工作量。有关虚拟线程的背景介绍,大家可以参阅 JEP 444。https:...【详细内容】
2023-12-27  Search: 虚拟线程  点击:(153)  评论:(0)  加入收藏
Java 21 的虚拟线程:高性能并发应用的福音
Java 21 最重要的特性之一就是虚拟线程 (JEP 444)。这些轻量级的线程降低了编写、维护和观察高吞吐量并行应用所需的努力。在讨论新特性之前,让我们先看一下当前的状态,以便更...【详细内容】
2023-12-08  Search: 虚拟线程  点击:(247)  评论:(0)  加入收藏
SpringBoot+虚拟线程,接口吞吐量成倍增加,太爽了!
在这篇博客中,我们将看到如何在spring-boot中利用loom虚拟线程。我们还将在JMeter的帮助下做一些负载测试,看看虚拟线程和普通线程的响应时间如何。首先,虚拟线程是 Project Lo...【详细内容】
2023-11-21  Search: 虚拟线程  点击:(59)  评论:(0)  加入收藏
虚拟线程原理及性能分析
一、背景JDK21 在 9 月 19 号正式发布,带来了较多亮点,其中虚拟线程备受瞩目,毫不夸张的说,它改变了高吞吐代码的编写方式,只需要小小的变动就可以让目前的 IO 密集型程序的吞吐...【详细内容】
2023-11-07  Search: 虚拟线程  点击:(232)  评论:(0)  加入收藏
搞懂为什么选择 Java 虚拟线程?
Hello folks,我是 Luga,今天我们来聊一下 Java 生态的核心技术&mdash;&mdash; Java Virtual Threads,即 “Java 虚拟线程” 。虚拟线程是 Java 中的一个重要创新,在 Project Loo...【详细内容】
2023-11-03  Search: 虚拟线程  点击:(256)  评论:(0)  加入收藏
什么是虚拟线程?一次启1000万个会OOM吗?
虚拟线程是在Java并发领域添加的一个新概念,那么虚拟线程到底是做什么用的呢?根据JEP中的内容告诉我们,虚拟线程是一种轻量级线程,可以显著地帮助我们减少编写、维护、观察高吞...【详细内容】
2023-10-30  Search: 虚拟线程  点击:(218)  评论:(0)  加入收藏
为什么Java官方不推荐池化虚拟线程?
一句话定义虚拟线程是在用户空间(而非内核)中实现的轻量级线程,其创建、调度和销毁由应用程序自己管理,而不依赖于操作系统。与传统线程区别传统的线程(也称为物理线程或内核线程...【详细内容】
2023-09-28  Search: 虚拟线程  点击:(289)  评论:(0)  加入收藏
▌简易百科推荐
即将过时的 5 种软件开发技能!
作者 | Eran Yahav编译 | 言征出品 | 51CTO技术栈(微信号:blog51cto) 时至今日,AI编码工具已经进化到足够强大了吗?这未必好回答,但从2023 年 Stack Overflow 上的调查数据来看,44%...【详细内容】
2024-04-03    51CTO  Tags:软件开发   点击:(5)  评论:(0)  加入收藏
跳转链接代码怎么写?
在网页开发中,跳转链接是一项常见的功能。然而,对于非技术人员来说,编写跳转链接代码可能会显得有些困难。不用担心!我们可以借助外链平台来简化操作,即使没有编程经验,也能轻松实...【详细内容】
2024-03-27  蓝色天纪    Tags:跳转链接   点击:(12)  评论:(0)  加入收藏
中台亡了,问题到底出在哪里?
曾几何时,中台一度被当做“变革灵药”,嫁接在“前台作战单元”和“后台资源部门”之间,实现企业各业务线的“打通”和全域业务能力集成,提高开发和服务效率。但在中台如火如荼之...【详细内容】
2024-03-27  dbaplus社群    Tags:中台   点击:(8)  评论:(0)  加入收藏
员工写了个比删库更可怕的Bug!
想必大家都听说过删库跑路吧,我之前一直把它当一个段子来看。可万万没想到,就在昨天,我们公司的某位员工,竟然写了一个比删库更可怕的 Bug!给大家分享一下(不是公开处刑),希望朋友们...【详细内容】
2024-03-26  dbaplus社群    Tags:Bug   点击:(5)  评论:(0)  加入收藏
我们一起聊聊什么是正向代理和反向代理
从字面意思上看,代理就是代替处理的意思,一个对象有能力代替另一个对象处理某一件事。代理,这个词在我们的日常生活中也不陌生,比如在购物、旅游等场景中,我们经常会委托别人代替...【详细内容】
2024-03-26  萤火架构  微信公众号  Tags:正向代理   点击:(10)  评论:(0)  加入收藏
看一遍就理解:IO模型详解
前言大家好,我是程序员田螺。今天我们一起来学习IO模型。在本文开始前呢,先问问大家几个问题哈~什么是IO呢?什么是阻塞非阻塞IO?什么是同步异步IO?什么是IO多路复用?select/epoll...【详细内容】
2024-03-26  捡田螺的小男孩  微信公众号  Tags:IO模型   点击:(8)  评论:(0)  加入收藏
为什么都说 HashMap 是线程不安全的?
做Java开发的人,应该都用过 HashMap 这种集合。今天就和大家来聊聊,为什么 HashMap 是线程不安全的。1.HashMap 数据结构简单来说,HashMap 基于哈希表实现。它使用键的哈希码来...【详细内容】
2024-03-22  Java技术指北  微信公众号  Tags:HashMap   点击:(11)  评论:(0)  加入收藏
如何从头开始编写LoRA代码,这有一份教程
选自 lightning.ai作者:Sebastian Raschka机器之心编译编辑:陈萍作者表示:在各种有效的 LLM 微调方法中,LoRA 仍然是他的首选。LoRA(Low-Rank Adaptation)作为一种用于微调 LLM(大...【详细内容】
2024-03-21  机器之心Pro    Tags:LoRA   点击:(12)  评论:(0)  加入收藏
这样搭建日志中心,传统的ELK就扔了吧!
最近客户有个新需求,就是想查看网站的访问情况。由于网站没有做google的统计和百度的统计,所以访问情况,只能通过日志查看,通过脚本的形式给客户导出也不太实际,给客户写个简单的...【详细内容】
2024-03-20  dbaplus社群    Tags:日志   点击:(4)  评论:(0)  加入收藏
Kubernetes 究竟有没有 LTS?
从一个有趣的问题引出很多人都在关注的 Kubernetes LTS 的问题。有趣的问题2019 年,一个名为 apiserver LoopbackClient Server cert expired after 1 year[1] 的 issue 中提...【详细内容】
2024-03-15  云原生散修  微信公众号  Tags:Kubernetes   点击:(6)  评论:(0)  加入收藏
站内最新
站内热门
站内头条