0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

探索虚拟线程:原理与实现

京东云 来源:jf_75140285 作者:jf_75140285 2024-06-24 11:35 次阅读

虚拟线程的引入与优势

在Loom项目之前,Java虚拟机(JVM)中的线程是通过java.lang.Thread类型来实现的,这些线程被称为平台线程。

然而,平台线程的创建和维护在资源使用上存在显著的开销。首先,创建成本不菲,因为每当操作系统需要创建一个新的平台线程时,它必须分配大量的内存(通常以兆字节计)来存储线程的上下文信息、本机栈和Java调用栈。这一过程受到固定大小堆栈的限制,导致创建和调度平台线程时的开销在空间和时间上都相当巨大。此外,当调度器需要从当前执行的线程中抢占时,必须处理大量内存的移动,这进一步增加了操作的复杂性和成本。这种开销不仅限制了可以同时创建的线程数量,而且也容易导致内存资源的耗尽。以下是一个示例,展示了在Java中如何通过不断实例化新的平台线程,迅速达到内存耗尽的情况:

private static void stackOverflowErrorDemo() {
    try {
        int threadCount = 0;
        // 尝试创建高达百万级的线程数量
        while (threadCount++ < 100000000) {
            // 创建并启动一个新线程
            Thread thread = new Thread(() - > {
                try {
                    // 线程休眠1秒,模拟长时间运行的任务
                    Thread.sleep(Duration.ofSeconds(1));
                } catch (InterruptedException e) {
                    // 如果线程被中断,将其转换为运行时异常
                    throw new RuntimeException(e);
                }
            });
            // 启动线程
            thread.start();
        }
    } catch (RuntimeException e) {
        // 捕获并处理由线程启动过程中可能抛出的运行时异常
        e.printStackTrace();
    }
}

在实际操作中,达到OutOfMemoryError的时间会根据操作系统和硬件的不同而有所差异。然而,通常情况下,这个过程可以在极短的时间内完成。

为了解决这些问题,虚拟线程应运而生。

虚拟线程的优势

资源效率:虚拟线程在内存使用上更为高效,初始内存占用通常只有几百字节,远小于平台线程所需的几兆字节。

简化线程管理:虚拟线程的创建和管理过程更为简便,通过工厂方法可以轻松创建,无需手动管理线程资源。

避免线程爆炸:由于资源消耗低,虚拟线程可以处理大量并发任务,而不必担心资源耗尽。

协作调度:虚拟线程采用协作调度模型,减少了锁竞争和上下文切换的开销,提升了多线程程序的性能。

避免阻塞:虚拟线程在遇到阻塞操作时可以释放执行权,允许其他线程执行,提高了程序的响应性。

虚拟线程如何创建

创建虚拟线程是Java中的一项新特性,它旨在解决传统平台线程所面临的资源限制问题。虚拟线程作为java.lang.Thread的一个替代实现,其独特之处在于将线程的调用堆栈存储在Java堆内存中,而不是传统的本地线程堆栈中。这种方式显著减少了每个线程所需的初始内存占用,通常仅为几百字节,而不是几兆字节。更进一步,虚拟线程的堆栈大小是动态可变的,这使得我们无需为各种用例预分配大量内存。以下是创建虚拟线程的两种方法:

使用工厂方法创建虚拟线程

通过java.lang.Thread的ofVirtual静态工厂方法,我们可以轻松创建虚拟线程。首先,定义一个辅助函数来创建并启动一个带有指定名称的虚拟线程:

private static Thread createVirtualThread(String name, Runnable runnable) {
    return Thread.ofVirtual()
            .name(name)
            .start(runnable);
}

使用ThreadPerTaskExecutor创建虚拟线程

另一种方法是使用专为虚拟线程设计的java.util.concurrent.ExecutorService实现,即ThreadPerTaskExecutor。这个执行器为提交的每个任务创建一个新的虚拟线程:

@SneakyThrows
static void createVirtualThreadUsingExecutorsWithName() {
  final ThreadFactory factory = Thread.ofVirtual().name("worker-", 0).factory();
  try (var executor = Executors.newThreadPerTaskExecutor(factory)) {
    var cleanTime =
      executor.submit(
        () -> {
          log.info("我要打扫卫生");
          sleep(Duration.ofMillis(500L));
          log.info("卫生打扫完了");
         });
    var boilingWater =
      executor.submit(
        () -> {
          log.info("我要去烧一些水");
          sleep(Duration.ofSeconds(1L));
          log.info("水烧好了");
        });
    cleanTime.get();
    boilingWater.get();
  }
}

在这个示例中,我们使用了submit方法来启动虚拟线程,它需要一个Runnable或Callable任务。submit方法返回一个Future对象,该对象可以用来跟踪和控制虚拟线程的执行。

虚拟线程的启动和同步

与平台线程相比,虚拟线程的启动和同步方式略有不同,因为它们是通过ExecutorService来管理的。每个submit调用都返回一个Future对象,这允许我们跟踪任务的状态,甚至在必要时阻塞当前线程直到虚拟线程完成其任务。

虚拟线程的原理

wKgaomZ46XiAVMJUAAM2pHHfAVk296.png



如上图所示展示虚拟线程与平台线程之间的关系:

JVM维护了一个由专用ForkJoinPool创建和维护的平台线程池。最初,平台线程的数量等于CPU核心的数量,最多不能超过256个。

对于每个创建的虚拟线程,JVM都会将其执行调度到一个平台线程上,临时将虚拟线程的堆栈块从堆复制到平台线程的堆栈中。我们说平台线程变成了虚拟线程的载体线程。

我们可以通过运行使用ThreadPerTaskExecutor创建虚拟线程的用例,观察其中的一条日志来说明执行过程:

10:30:35.390 [worker-1] INFO in.rcard.virtual.threads.App - VirtualThread[#23,worker-1]/runnable@ForkJoinPool-1-worker-2 | 我要去烧一些水

从日志中进行观察

1. 线程标识与命名:每个虚拟线程都有一个唯一的标识符和名称,例如 `VirtualThread[#23,worker-1]`。这里的 `#23` 表示线程的编号,而 `worker-1` 是线程的名称,它们共同帮助开发者识别和调试线程。

2. 载体线程的分配:虚拟线程执行时,会绑定到一个特定的载体线程(即平台线程)。例如,`ForkJoinPool-1-worker-2` 表示该虚拟线程正在由默认的ForkJoinPool中的第二个工作线程执行。

3. 阻塞与释放:当虚拟线程遇到阻塞操作时,其载体线程会被释放,以便能够执行其他就绪的虚拟线程。同时,虚拟线程的堆栈块会从载体线程的堆栈复制回Java堆中,以等待阻塞操作的完成。

4. 再次调度:一旦虚拟线程完成其阻塞操作,调度器会将其重新排入执行队列。虚拟线程可能会继续在先前的载体线程上执行,或者根据调度器的决策,在不同的载体线程上继续执行。

刚才我们提到,默认情况下,JVM会创建与cpu核心数量相等的载体线程(平台线程),以确保每个物理核心都能被有效利用。那么假如计算机上配备了2个物理核心和通过超线程技术支持的4个逻辑核心,基于此硬件配置,我们可以设计一个程序,该程序旨在生成与逻辑核心数相匹配的虚拟线程数量,即4个虚拟线程。然而,为了探索线程调度的灵活性,我们可以增加一个额外的虚拟线程,使得总数达到5个,即期望5个虚拟线程在4个载体线程上执行,那么至少会有一个载体线程会被重复使用。执行以下程序

static void viewCarrierThreadPoolSize() {
  final ThreadFactory factory = Thread.ofVirtual().name("worker-", 0).factory();
  try (var executor = Executors.newThreadPerTaskExecutor(factory)) {
    IntStream.range(0, numberOfCores() + 1)
        .forEach(i -> executor.submit(() -> {
          log.info("virtual thread number " + i);
          sleep(Duration.ofSeconds(1L));
        }));
  }
}
[worker-0] INFO in.rcard.virtual.threads.App - VirtualThread[#21,worker-0]/runnable@ForkJoinPool-1-worker-1 | virtual thread number 0
[worker-1] INFO in.rcard.virtual.threads.App - VirtualThread[#23,worker-1]/runnable@ForkJoinPool-1-worker-2 | virtual thread number 1
[worker-2] INFO in.rcard.virtual.threads.App - VirtualThread[#24,worker-2]/runnable@ForkJoinPool-1-worker-3 | virtual thread number 2
[worker-4] INFO in.rcard.virtual.threads.App - VirtualThread[#26,worker-4]/runnable@ForkJoinPool-1-worker-4 | virtual thread number 4
[worker-3] INFO in.rcard.virtual.threads.App - VirtualThread[#25,worker-3]/runnable@ForkJoinPool-1-worker-4 | virtual thread number 3

观察日志,有四个载体线程,分别是ForkJoinPool-1-worker-1、ForkJoinPool-1-worker-2、ForkJoinPool-1-worker-3和ForkJoinPool-1-worker-4,ForkJoinPool-1-worker-4被重复使用了两次,以上假设正确。

审核编辑 黄宇

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 存储
    +关注

    关注

    13

    文章

    4367

    浏览量

    86259
  • 操作系统
    +关注

    关注

    37

    文章

    6905

    浏览量

    123870
  • 虚拟
    +关注

    关注

    0

    文章

    189

    浏览量

    23711
  • 虚拟机
    +关注

    关注

    1

    文章

    950

    浏览量

    28495
  • 线程
    +关注

    关注

    0

    文章

    507

    浏览量

    19769
收藏 人收藏

    评论

    相关推荐

    摩尔线程宣布成功部署DeepSeek蒸馏模型推理服务

    近日,摩尔线程智能科技(北京)有限责任公司在其官方渠道发布了一则重要消息,宣布公司已经成功实现了对DeepSeek蒸馏模型推理服务的部署。这一技术突破,标志着摩尔线程在人工智能领域迈出了坚实的一步
    的头像 发表于 02-06 13:49 308次阅读

    探索字节队列的魔法:多类型支持、函数重载与线程安全

    探索字节队列的魔法:多类型支持、函数重载与线程安全代码难度指数:文章学习重点:参数宏的使用技巧一、引言在嵌入式系统和实时应用中,数据的传输和处理是至关重要的。字节队列(ByteQueue)是一种重要
    的头像 发表于 11-15 01:08 920次阅读
    <b class='flag-5'>探索</b>字节队列的魔法:多类型支持、函数重载与<b class='flag-5'>线程</b>安全

    socket 多线程编程实现方法

    在现代网络编程中,多线程技术被广泛应用于提高服务器的并发处理能力。Socket编程是网络通信的基础,而将多线程技术应用于Socket编程,可以显著提升服务器的性能。 多线程编程的基本概念 多线
    的头像 发表于 11-12 14:16 499次阅读

    虚拟桌面基础架构(VDI)远程连接如何实现

    今天我们来聊聊虚拟桌面基础架构(VDI)。VDI的工作原理是将桌面虚拟化,使得用户可以通过网络连接访问虚拟机,确保数据和应用保存在服务器上,提高数据的安全性。使用VDI通常涉及以下步骤1、部署
    的头像 发表于 10-18 08:09 407次阅读
    <b class='flag-5'>虚拟</b>桌面基础架构(VDI)远程连接如何<b class='flag-5'>实现</b>

    CPU线程和程序线程的区别

    CPU的线程与程序的线程在概念、作用、实现方式以及性能影响等方面存在显著差异。以下是对两者区别的详细阐述,旨在深入探讨这一技术话题。
    的头像 发表于 09-02 11:18 1310次阅读

    Java CompletableFuture 异步超时实现探索

    简介 JDK 8 中 CompletableFuture 没有超时中断任务的能力。现有做法强依赖任务自身的超时实现。本文提出一种异步超时实现方案,解决上述问题。 前言 JDK 8 是一次重大的版本
    的头像 发表于 07-25 14:06 439次阅读

    鸿蒙开发:线程模型

    FA模型下的线程主要有如下三类
    的头像 发表于 06-24 17:27 495次阅读
    鸿蒙开发:<b class='flag-5'>线程</b>模型

    鸿蒙开发:【线程模型】

    管理其他线程的ArkTS引擎实例,例如使用TaskPool(任务池)创建任务或取消任务、启动和终止Worker线程
    的头像 发表于 06-13 16:38 476次阅读
    鸿蒙开发:【<b class='flag-5'>线程</b>模型】

    动态线程池思想学习及实践

    ://www.javadoop.com/post/java-thread-pool 引言 在后台项目开发过程中,我们常常借助线程池来实现线程任务,以此提升系统的吞吐率和响应性;而线程
    的头像 发表于 06-13 15:43 1269次阅读
    动态<b class='flag-5'>线程</b>池思想学习及实践

    摩尔线程和滴普科技完成大模型训练与推理适配

    近日,摩尔线程与滴普科技宣布了一项重要合作成果。摩尔线程的夸娥(KUAE)千卡智算集群与滴普科技的企业大模型Deepexi已完成训练及推理适配,共同实现了700亿参数LLaMA2大语言模型的预训练测试。
    的头像 发表于 05-30 10:14 624次阅读

    摩尔线程携手憨猴集团,共同探索国产AI算力的新应用场景与落地实践

    摩尔线程与憨猴科技集团有限公司(简称:憨猴集团)近日宣布,基于摩尔线程的夸娥(KUAE)千卡智算集群,憨猴集团已成功完成了7B、34B、70B不同参数量级的大模型分布式训练任务。
    的头像 发表于 05-17 15:08 517次阅读

    苹果公司获取新专利,无需手写笔,虚拟场景绘画轻松实现

    首项专利为《适用于头戴式设备,基于触控笔的输入系统》。据该专利披露,苹果正探索通过触控笔实现虚拟世界的互动。实际上,虽然Apple Pencil为理想的输入设备
    的头像 发表于 03-29 10:23 426次阅读

    java实现线程的几种方式

    Java实现线程的几种方式 多线程是指程序中包含了两个或以上的线程,每个线程都可以并行执行不同的任务或操作。Java中的多
    的头像 发表于 03-14 16:55 797次阅读

    python中5种线程锁盘点

    线程安全是多线程或多进程编程中的一个概念,在拥有共享数据的多条线程并行执行的程序中,线程安全的代码会通过同步机制保证各个线程都可以正常且正确
    发表于 03-07 11:08 1693次阅读
    python中5种<b class='flag-5'>线程</b>锁盘点

    什么是动态线程池?动态线程池的简单实现思路

    因此,动态可监控线程池一种针对以上痛点开发的线程池管理工具。主要可实现功能有:提供对 Spring 应用内线程池实例的全局管控、应用运行时动态变更线
    的头像 发表于 02-28 10:42 747次阅读