Java多线程基础:虚拟线程与平台线程解析

在这篇文章中,主要总结一些关于线程的概念,以及更近期的名为虚拟线程的特性。将了解平台线程和虚拟线程在性质上的区别,以及它们如何促进应用程序性能的改进
在这里插入图片描述

经典线程背景:

让我们以调用外部API或某些数据库交互的场景为例,看看线程执行的生命周期。

  1. 线程被创建并准备在内存中提供服务。
  2. 一旦请求到达,它被映射到其中一个线程,然后通过调用外部API或执行某些数据库查询来提供服务。
  3. 线程等待,直到它从服务或数据库获取到响应。
  4. 一旦收到响应,它执行后响应的活动并返回到线程池。

在这里插入图片描述
观察上述生命周期中的第3步,即线程只是等待且什么都不做。这是一个主要的缺点,通过仅等待而未充分利用系统资源,大多数线程在其生命周期中只是等待响应而无所作为。

在Java 19或更高版本之前,创建线程或现有线程的标准方式被称为本地线程或平台线程。在这种架构风格中,平台线程与操作系统线程之间存在一对一的映射。这意味着操作系统线程被低效使用,因为它只是等待活动完成而无所作为,从而使它们变得沉重且昂贵。

虚拟线程:

Java中的虚拟线程代表了Java处理并发和多线程的重要演变。作为Oracle的项目Loom的一部分引入,该项目是Oracle解决编写、维护和观察高吞吐并发应用程序所面临挑战的倡议。虚拟线程被设计为轻量级,并使并发对开发人员更加容易。

虚拟线程是由Java虚拟机(JVM)管理的轻量级线程,而不是由操作系统管理。与平台线程不同,虚拟线程创建和销毁成本较低。它们映射到较少的平台线程,使Java应用程序能够以更低的资源占用同时处理数千甚至数百万个任务。

在这里插入图片描述

虚拟线程在平台线程上的有用性

虚拟线程相对于平台线程的优点是多方面的。首先,它们能够更有效地利用系统资源。由于虚拟线程轻量级,与平台线程相比,它们消耗更少的内存和CPU资源。这种效率允许更高程度的并发,使得能够在单个JVM上运行大量并发任务。

其次,虚拟线程简化了Java中的并发编程。它们允许开发人员以直观、命令式的风格编写代码,类似于编写同步代码的方式,而不必处理异步编程模型的复杂性。这种简单性降低了常见的与并发相关的错误的可能性,如死锁和竞争条件。

此外,虚拟线程促进了更好的CPU利用率。在传统的线程模型中,大量的CPU时间可能会在管理和在许多线程之间进行上下文切换方面浪费。虚拟线程减少了与上下文切换相关的开销,允许更有效地执行并发任务。

实际应用:

如果我们需要创建经典的平台线程来完成任务,我们可以按照以下步骤操作。创建一个名为PlatformThreadDemo.java的文件,并将内容复制如下。

package org.vaslabs;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class PlatformThreadDemo {
    private static final Logger logger = LoggerFactory.getLogger(PlatformThreadDemo.class);

    public static void main(String[] args) {
        attendMeeting().start();
        completeLunch().start();
    }

    private static Thread attendMeeting(){
        var message = "Platform Thread [Attend Meeting]";
        return new Thread(() -> {
            logger.info(STR."{} | \{message}", Thread.currentThread());
        });
    }

    private static Thread completeLunch(){
        var message = "Platform Thread [Complete Lunch]";
        return new Thread(() -> {
            logger.info(STR."{} | \{message}", Thread.currentThread());
        });
    }

    // using builder pattern to create platform threads
    private static void attendMeeting1(){
        var message = "Platform Thread [Attend Meeting]";
        Thread.ofPlatform().start(() -> {
            logger.info(STR."{} | \{message}", Thread.currentThread());
        });
    }

    private static void completeLunch1(){
        var message = "Platform Thread [Complete Lunch]";
        Thread.ofPlatform().start(() -> {
            logger.info(STR."{} | \{message}", Thread.currentThread());
        });
    }
}

上述示例展示了两种创建平台线程的方法:

  1. 使用Thread构造函数并将可运行的lambda传递给它。
  2. 使用Thread的Platform()构建方法。
    让我们通过创建一些并发的虚拟线程来看更复杂的编码。创建一个名为DailyRoutineWorkflow.java的文件,并将下面的代码复制到其中。
package org.vaslabs;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.TimeUnit;

public class DailyRoutineWorkflow {
    static final Logger logger = LoggerFactory.getLogger(DailyRoutineWorkflow.class);

    static void log(String message) {
        logger.info(STR."{} | \{message}", Thread.currentThread());
    }


    private static void sleep(Long duration) throws InterruptedException {
        TimeUnit.MILLISECONDS.sleep(duration);
    }

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

    static Thread attendMorningStatusMeeting() {
        return virtualThread(
                "Morning Status Meeting",
                () -> {
                    log("I'm going to attend morning status meeting");
                    try {
                        sleep(1000L);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    log("I'm done with morning status meeting");
                });
    }

    static Thread workOnTasksAssigned() {
        return virtualThread(
                "Work on the actual Tasks",
                () -> {
                    log("I'm starting my actual work on tasks");
                    try {
                        sleep(1000L);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    log("I'm done with actual work on tasks");
                });
    }

    static Thread attendEveningStatusMeeting() {
        return virtualThread(
                "Evening Status Meeting",
                () -> {
                    log("I'm going to attend evening status meeting");
                    try {
                        sleep(1000L);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    log("I'm done with evening status meeting");
                });
    }

    static void concurrentRoutineExecutor() throws InterruptedException {
        var morningMeeting = attendMorningStatusMeeting();
        var actualWork = workOnTasksAssigned();
        var eveningMeeting = attendEveningStatusMeeting();
        morningMeeting.join();
        actualWork.join();
        eveningMeeting.join();
    }
}

上述代码展示了使用工厂方法创建虚拟线程。除了工厂方法之外,我们还可以使用专为虚拟线程定制的java.util.concurrent.ExecutorService来实现虚拟线程,称为java.util.concurrent.ThreadPerTaskExecutor。您可以使用ExecutorService获得与上述相同的功能,如下所示。

package org.vaslabs;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class DailyRoutineWorkflowUsingExecutors {
    static final Logger logger = LoggerFactory.getLogger(DailyRoutineWorkflowUsingExecutors.class);

    static void log(String message) {
        logger.info(STR."{} | \{message}", Thread.currentThread());
    }


    private static void sleep(Long duration) throws InterruptedException {
        TimeUnit.MILLISECONDS.sleep(duration);
    }

    public static void executeJobRoute() throws ExecutionException, InterruptedException {
        try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
            var morningMeeting = executor.submit(() -> {
                log("I'm going to attend morning status meeting");
                try {
                    sleep(1000L);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                log("I'm done with morning status meeting");
            });

            var actualWork = executor.submit(() -> {
                log("I'm starting my actual work on tasks");
                try {
                    sleep(1000L);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                log("I'm done with actual work on tasks");
            });

            var eveningMeeting = executor.submit(() -> {
                log("I'm going to attend evening status meeting");
                try {
                    sleep(1000L);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                log("I'm done with evening status meeting");
            });

            morningMeeting.get();
            actualWork.get();
            eveningMeeting.get();
        }
    }
}

深入了解输出

如果您运行上述代码,无论是使用工厂方法还是ExecutorServices,您将看到类似于以下的输出。
在这里插入图片描述
仔细观察信息日志,您将看到“|”(管道符号)两侧的两个部分,第一部分解释了有关虚拟线程的信息,如VirtualThread[#26]/runnable@ForkJoinPool-1-worker-3。这告诉我们VirtualThread[#26]映射到平台线程runnable@ForkJoinPool-1-worker-3,而另一部分是日志的信息部分。

ThreadLocals和虚拟线程:

在Java中,ThreadLocal是一种机制,允许变量基于每个线程进行存储。访问ThreadLocal变量的每个线程都会获得其自己独立初始化的变量副本,可以在不影响其他线程中相同变量的情况下进行访问和修改。这在您想要保持特定于线程的状态(例如用户会话或数据库连接)的情景中特别有用。

然而,当与作为Loom项目的一部分引入的虚拟线程一起使用时,ThreadLocal的行为发生了显著变化。虚拟线程是由Java虚拟机(JVM)管理的轻量级线程,设计用于大量调度,而不同于与操作系统的线程管理相关联的传统平台线程。

由于可以创建数百万个虚拟线程,使用ThreadLocal可能导致内存泄漏。

package org.vaslabs;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.TimeUnit;

public class ThreadLocalDemo {
    private static ThreadLocal<String> stringThreadLocal = new ThreadLocal<>();

    static final Logger logger = LoggerFactory.getLogger(DailyRoutineWorkflow.class);

    static void log(String message) {
        logger.info(STR."{} | \{message}", Thread.currentThread());
    }

    private static void sleep(Long duration) throws InterruptedException {
        TimeUnit.MILLISECONDS.sleep(duration);
    }

    public static void virtualThreadContext() throws InterruptedException {
        var virtualThread1 = Thread.ofVirtual().name("thread-1").start(() -> {
            stringThreadLocal.set("thread-1");
            try {
                sleep(1000L);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            log(STR."thread name is \{stringThreadLocal.get()}");
        });
        var virtualThread2 = Thread.ofVirtual().name("thread-2").start(() -> {
            stringThreadLocal.set("thread-2");
            try {
                sleep(1000L);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            log(STR."thread name is \{stringThreadLocal.get()}");
        });
        virtualThread1.join();
        virtualThread2.join();
    }
}

总结:

虚拟线程成为一个颠覆性的变革者,提供了轻量级、高效的并发性,与平台线程资源密集型的特性形成鲜明对比。它们通过在最小资源开销下使大量并发任务成为可能,从而改变了Java处理多线程的方式,简化了编程模型并增强了应用程序的可扩展性。

然而,在这个新背景下对ThreadLocal的使用的复杂性突显了需要谨慎考虑的必要性。虽然ThreadLocal在传统线程中保持特定于线程的数据方面仍然是一个强大的工具,但在虚拟线程中,它的应用变得更加复杂,需要替代策略来进行状态和上下文管理。这些概念共同标志着Java并发范式的重大转变,为开发人员构建更具响应性、可扩展性和高效性的应用程序打开了新的大门。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/327060.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

JVM篇--Java内存区域高频面试题

java内存区域 1 Java 堆空间及 GC&#xff1f; 首先我们要知道java堆空间的产生过程&#xff1a; 即当通过java命令启动java进程的时候&#xff0c;就会为它分配内存&#xff0c;而分配内存的一部分就会用于创建堆空间&#xff0c;而当程序中创建对象的时候 就会从堆空间来分…

918. 环形子数组的最大和

参考题解&#xff1a;https://leetcode.cn/problems/maximum-sum-circular-subarray/solutions/1152143/wo-hua-yi-bian-jiu-kan-dong-de-ti-jie-ni-892u/ class Solution {public int maxSubarraySumCircular(int[] nums) {int n nums.length;int sum nums[0], minSum nums…

【目标跟踪】跨相机如何匹配像素

文章目录 前言一、计算思路二、代码三、结果 前言 本本篇博客介绍一种非常简单粗暴的方法&#xff0c;做到跨相机像素匹配。已知各相机内外参&#xff0c;计算共视区域像素投影&#xff08;不需要计算图像特征&#xff09;。废话不多说&#xff0c;直接来&#xff0c;见下图。…

快速入门Java NIO(Not I/O)的网络通信框架--Netty

Netty 入门 了解netty前需要对nio有一定认识,该笔记基础来自bilinbili黑马,在此基础上自己学习的笔记,添加了一些自己的理解 了解java 非阻塞io编程 1. 概述 1.1 Netty 是什么&#xff1f; Netty is an asynchronous event-driven network application framework for rapid …

掌握这些测试开发技能,从容应对工作难题!

各位小伙伴, 大家好, 本期为大家分享一些测试开发工程师在企业中通过哪些测试开发技能解决难题。 一.如何定位缺陷 在企业中, 小伙伴们在发现bug后, 需要定位到具体产生bug的原因, 在这种情况下, 我们可以通过以下几种方案: 1.通过代理抓包来分析 常用的抓包工具有: Charles…

R语言【paleobioDB】——pbdb_subtaxa():统计指定类群下的子类群数量

Package paleobioDB version 0.7.0 paleobioDB 包在2020年已经停止更新&#xff0c;该包依赖PBDB v1 API。 可以选择在Index of /src/contrib/Archive/paleobioDB (r-project.org)下载安装包后&#xff0c;执行本地安装。 Usage pbdb_subtaxa (data, do.plot, col) Arguments…

Monorepo-uniapp 构建分享

Monorepo uniapp 构建灵感&#xff1a;刚好要做一个项目&#xff0c;于是想到升级一下之前自己写的一个vue3tspiniauno的模版框架&#xff0c;其实那个框架也不错&#xff1b;只是感觉还差点东西&#xff0c;我已经用那个小框架写了两三个项目&#xff1b;轻巧实用。为什么选…

CNN:Convolutional Neural Network(上)

目录 1 为什么使用 CNN 处理图像 2 CNN 的整体结构 2.1 Convolution 2.2 Colorful image 3 Convolution v.s. Fully Connected 4 Max Pooling 5 Flatten 6 CNN in Keras 原视频&#xff1a;李宏毅 2020&#xff1a;Convolutional Neural Network 1 为什么使用…

C#灵活控制多线程的状态(开始暂停继续取消)

ManualResetEvent类 ManualResetEvent是一个同步基元&#xff0c;用于在多线程环境中协调线程的执行。它提供了两种状态&#xff1a;终止状态和非终止状态。 在终止状态下&#xff0c;ManualResetEvent允许线程继续执行。而在非终止状态下&#xff0c;ManualResetEvent会阻塞线…

深度学习-标注文件处理(txt批量转换为json文件)

接上篇&#xff0c;根据脚本可将coco128的128张图片&#xff0c;按照比例划分成训练集、测试集、验证集&#xff0c;同时生成相应的标注的labels文件夹&#xff0c;最近再看实例分离比较火的mask rcnn模型&#xff0c;准备进行调试但由于实验室算力不足&#xff0c;网上自己租的…

stm32 - GPIO

stm32 - GPIO 基本结构输入输出 基本结构 所有GPIO都挂在APB2总线上 寄存器&#xff1a;内核通过APB2总线对寄存器进行读写&#xff0c;实现电平的读写 GPIO引脚的每一位对应寄存器中的某一位 GPIO中的驱动器是增加信号驱动能力的&#xff0c;用于增大驱动能力 输入 读取端口的…

初识C语言·内存函数

目录 1 memcpy的使用和模拟实现 2 memmove的使用和模拟实现 3 memset的使用和模拟实现 4 memcmp的使用和模拟实现 1 memcpy的使用和模拟实现 紧接字符串函数&#xff0c;出场的是第一个内存函数memcpy。前面讲的字符串函数是专门干关于字符串的事的&#xff0c;而这个函数…

如何使用程序控制微信发送消息

简介 使用杨中科老师的nuget包NetAutoGUI&#xff0c;控制微信给指定用户发送消息&#xff0c;如果想下面视频一样使用此功能用来轰炸朋友&#xff0c;可以直接跳到最后一节&#xff0c;或者直接下载我的打包好的程序集 【免费】控制微信发送消息的程序资源-CSDN文库 微信轰炸…

蓝桥杯备赛 | 洛谷做题打卡day5

蓝桥杯备赛 | 洛谷做题打卡day5 图论起航&#xff0c;一起来看看深&#xff08;广&#xff09;度优先吧 ~ 文章目录 蓝桥杯备赛 | 洛谷做题打卡day5图论起航&#xff0c;一起来看看深&#xff08;广&#xff09;度优先吧 ~【深基18.例3】查找文献题目描述 输入格式输出格式样例…

《如何制作类mnist的金融数据集》——1.数据集制作思路

1&#xff0e;数据集制作思路&#xff08;生成用于拟合金融趋势图像的分段线性函数&#xff09; 那么如何去制作这样的一个类minist的金融趋势曲线数据集呢&#xff1f; 还是如上图所示&#xff0c;为了使类别平均分布&#xff0c;因此可以选取三种“buy”的曲线、三种“sell”…

Web前端 ---- 【Vue3】computed计算属性和watch侦听属性(侦听被ref和reactive包裹的数据)

目录 前言 computed watch watch侦听ref数据 ref简单数据类型 ref复杂数据类型 watch侦听reactive数据 前言 本文介绍在vue3中的computed计算属性和watch侦听属性。介绍watch如何侦听被ref和reactive包裹的数据 computed 在vue3中&#xff0c;计算属性computed也是组合式…

C语言天花板——指针(经典题目)

指针我们已经学习的差不多了&#xff0c;今天我来给大家分享几个经典的题目&#xff0c;来让我们相互学习&#x1f3ce;️&#x1f3ce;️&#x1f3ce;️ int main() {int a[4] { 1, 2, 3, 4 };int* ptr1 (int*)(&a 1);int* ptr2 (int*)((int)a 1);printf("%x,%…

Java重修第六天—面向对象3

通过学习本篇文章可以掌握如下知识 1、多态&#xff1b; 2、抽象类&#xff1b; 3、接口。 之前已经学过了继承&#xff0c;static等基础知识&#xff0c;这篇文章我们就开始深入了解面向对象多态、抽象类和接口的学习。 多态 多态是在继承/实现情况下的一种现象&#xf…

随笔03 笔记整理

图源&#xff1a;文心一言 关于我的考研与信息安全类博文整理~&#x1f95d;&#x1f95d; 第1版&#xff1a;整理考研类博文~&#x1f9e9;&#x1f9e9; 第2版&#xff1a;提前列出博文链接&#xff0c;以便小伙伴查阅~&#x1f9e9;&#x1f9e9; 第3版&#xff1a;整理We…

学习记录-自动驾驶与机器人中的SLAM技术

以下所有内容均为高翔大神所注的《自动驾驶与机器人中的SLAM技术》中的内容 融合导航 1. EKF和优化的关系 2. 组合导航eskf中的预测部分&#xff0c;主要是F矩阵的构建 template <typename S> bool ESKF<S>::Predict(const IMU& imu) {assert(imu.timestamp…