每每聊到线程Thread

news2024/12/24 20:54:51

cc68d78eae42e18e7c0ae53b5b10b8d5.jpeg

1d91e3ee64e8000aaeb4e895676af6f6.gif

进程和线程

进程

所谓计算机程序 Program,其实就是通过执行一系列指令来完成某一个任务。当你启动一个程序时,操作系统(OS)会将其加载到内存中,并在内存中申请一块固定地址的命名空间(address space),并在此命名空间内执行相关指令。聪明人应该已经听出来了,这不就是"进程 Process" 嘛。没有错,某种程度上我们确实可以将进程理解为一个程序的。

线程

线程就是在进程内部,一系列可执行的独立指令。而这些独立指令最终是被 CPU 执行。为了更大利益化的使用 CPU,在一个进程Process内,可以存在多个线程。这就让程序有了并发执行指令的能力,增加了程序的吞吐量。

bdfeee4d17243aa6e8314dacc60d58e3.gif

CPU 和 并发

实际上大多数情况下,"并发"只是 CPU 给我们造成的一种"幻觉"。只有在线程数小于 CPU 核数时,才是真正意义的并发。

假设我们有一个 4 核CPU,然后同时开启4个线程执行任务,那在同一时间CPU的4核会分别执行1个线程的指令,如下图:

b54b6e6ae7c7bc04a05f6bc02ec36ff3.png

但是如果我们开启 8 个线程执行任务,因为CPU只有4核,所以只能通过切换上下文(switch-context)的方式来实现并发的"幻觉"。也就是在不同的线程间切换,因为切换时间极快,使用户从视觉上感知就是同时在执行的。如下图

42564b8785e3bc8e275008ab71f0f6aa.png

7310bbb52fe485161283bb32a9bc1fcd.gif

Thread 奇技淫巧

线程是一把"双刃剑",使用合理能够提高系统效率,使用不当也会造成揠苗助长。

假设我们有一个 int 类型变量 count,我们分别使用 1000 个线程使其自增1,以及使用单个线程使其自增1000次。我们可以对比下这2种方式所耗的时间,代码如下:

public class SumOfNums {
   volatile int count = 0;
   static long start, end;


   synchronized void increase_with_multi_thread() {
       count++;
       if (count == 2000) {
           end = System.currentTimeMillis();
           System.out.println("multi thread, start: " + start + " end:" + end + " time: " + (end - start));
       }
   }


   void increase_with_single_thread() {
       new Thread(() -> {
           for (int i = 0; i < 1000; i++) {
               count++;
           }
           end = System.currentTimeMillis();
           System.out.println("single thread, start: " + start + " end:" + end + " time: " + (end - start));
       }).start();
   }


   public static void main(String[] args) {
       SumOfNums sumOfNums = new SumOfNums();
       start = System.currentTimeMillis();
       sumOfNums.increase_with_single_thread();  // 1. 单线程自增 1000 次
       // 2. 创建 1000 个线程,使其自增1
       for (int i = 1; i <= 1000; i++) {
           Thread t = new Thread(sumOfNums::increase_with_multi_thread);
           t.start();
       }
   }
}

执行上述代码,得到如下结果:

single thread, start: 1685429904704 end:1685429904706 time: 2
multi thread, start: 1685429904704 end:1685429904748 time: 44

可以看出,创建过多的线程并没有提高工作效率,反而比单线程慢超过20倍!

5f35cb76d8a424cedd81b7969e3718ac.gif

问题分析

在上述将 count 自增的操作属于 CPU密集型任务,计算结果高度依赖 CPU 的计算效率。之所以在之前的结果里多个线程的效率慢,是因为我们创建了 1000 个线程,个数已经完全超出了CPU核数,所以 CPU 不得不在多个线程中进行上下文切换,这个操作会严重影响程序运行效率。

除了 CPU 密集型任务,还有一种 IO 密集型任务。假设我们需要读取磁盘空间上的 20 个文件,并对其内容进行计算。在磁盘读取时,CPU是处于 IDLE(闲置) 状态。因为磁盘读取操作是发生在特殊硬件设备(disk 驱动)中。此时不需要 CPU 的参与,只有读完磁盘之后,剩下的操作才交给 CPU 进行处理。假设我们还是在4核CPU上,使用 4 个线程执行此任务,使用整个过程如下图所示:

40586bfe235b846e77c9a6bec9b38817.png

可以看出,每一个线程被相应的分配给 CPU 的每个核。线程是充分利用了,但是当代码执行到磁盘读取操作时,CPU核就会处于 IDLE 状态,浪费了CPU资源。对于这种情况,如果我们就可以通过将线程数提高到8个来提高工作效率,如下图:

56f0ed542593d8d3e0bbc1e1b11b25ca.png

效率提高的原因是,在之前4个线程的 IDLE 状态,此时因为有额外线程需要执行。所以 CPU 会继续执行其它额外的线程任务,充分利用了 CPU 资源。总结就是:与其 IDLE,不如压榨!

1a472ecfddc9fe3190e19b503b840c37.gif

Thread 挖一挖

在现代操作系统中,Thread一般有2种类型:Kernel ThreadUser Thread

Kernel Thread

内核线程,又可以被叫做 OS Thread,也就是操作系统线程。Kernel 线程是由操作系统内核来管理,每一个线程都需要包含线程状态、优先级,以及一些其它属性。这类线程相对较重,需要调用操作系统的API来创建、维护管理。

User Thread

User Thread就是用户线程,是处于应用层面的一种数据结构体,比如我们使用 Java SDK 中的 Thread就是User Thread。在这种数据结构内部,同样也定义了线程的状态、优先级等属性。

但实际上User Thread 无法被直接执行,User Thread 需要 mapping 到 Kernel Thread 之后,被解释成指令后再执行。一共有3种mapping方式:

M:1 model:    所有的User Thread 都与1个Kernel Thread进行mapping
1:1 model:     1个User Thread对应2个Kernel Thread
M:N model:   所有的User Thread 对应的是系统层的 Kernel Thread Pool

be54fb643bd85c50372504e01a7776f1.gif

Java Thread

上面说的 mapping 方式是大多操作系统会使用的模型方式,那么在 Java JVM中是使用何种方式呢?

Green Thread

在 早期版本,Java 提供的是 Green Thread 模型方式,所有线程都是由 JVM 管理和调度。并且 Green Thread 与 Kernel Thread 的 mapping 模式是 M:1 方式,所以Green Thread的运行速度是极快的。但是 Green Thread 有一个比较大的缺陷:无法针对多核处理器进行扩展。

Native Thread

从 JDK 1.2 之后,Java 就放弃了 Green Thread 模型方式,改用 Native Thread 模型方式了。Native 同样也是有 JVM 管理,但是一定程度上对底层的Kernel有依赖;另外,Native Thread 与 Kenel Thread 的 mapping 模式是 1:1 模式。

 改用 Native Thread之后,这几番操作下来,在一定程度上提高了线程的运行效率,但是带来的后果就是线程的创建和销毁操作变得异常笨重。这也是为什么我们在项目中会使用线程池来缓存线程、提高运行效率的原因。

cab0569bc364e6c6ea8f21c59e15637a.gif

虚拟线程

Java 19 版本引入了虚拟线程 Virtual Thread,它是一种轻量级的线程。虚拟线程解决了 Native Thread 的缺陷,创建和销毁过程不再那么笨重,不需要操作系统的参与。

可以通过如下方式,使用虚拟线程:

for (int i = 0; i < 5; i++) {
   Thread vThread = Thread.ofVirtual().start(() -> System.out.println("Hello World"));
}

或者使用Executors创建虚拟线程:

public static void main(String[] args) {
    var executor = Executors.newVirtualThreadExecutor();


    for (int i = 0; i < 5; i++) {
        executor.submit(() -> System.out.println("Hello World"));
    }

    executor.awaitTermination();
    System.out.println("All virtual threads are finished");
}

ee132fe6ff7102fc47af78eb82836489.gif

如果你喜欢本文

长按二维码关注

7f14361df3b4bf2f088c2f7314f1023d.gif

85d133d96be3d2eb6a282d07786fc9a6.jpeg

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

bert中文文本摘要代码(2)

bert中文文本摘要代码 写在最前面关于BERT使用transformers库进行微调 model.py自定义参数激活函数geluswish定义激活函数字典 BertConfig类参数配置vocab_size_or_config_json_filefrom_dict方法&#xff08;from_json_file时调用&#xff09;from_json_file方法一系列方法 Be…

Coursera自动驾驶2.1——最小二乘法和线性和非线性卡尔曼滤波

文章目录 一、最小二乘法1.最小二乘法2.加权最小二乘法3.递归最小二乘法4.最小二乘法与极大似然 二、卡尔曼滤波1.概述2.线性卡尔曼滤波3.扩展卡尔曼滤波Error State卡尔曼滤波 4.无迹卡尔曼滤波&#xff08;1&#xff09;无迹变换&#xff08;2&#xff09;无迹卡尔曼滤波 一、…

I.MX6ull 中断 一

一 I.MX6ull 中断介绍 1 中断类型 Cortex-A7内核有8个异常中断&#xff0c;这8个异常中断的中断向量表如下表所示&#xff1a; Cortex-A 内核 CPU 的所有外部中 断都属于这个 IRQ 中断&#xff0c;当任意一个外部中断发生的时候都会触发 IRQ 中断。在 IRQ 中断服 务函数里面就…

linux php8.2安装swoole扩展

下载swoole源码&#xff1a; wget https://wenda-1252906962.file.myqcloud.com/dist/swoole-src-5.0.2.tar.gz tar -zxvf swoole-src-5.0.2.tar.gz cd swoole-src-5.0.2/ 初始化&#xff1a; /www/server/php/82/bin/phpize 配置检查&#xff1a; ./configure --with-…

JDK17在Windows安装以及环境变量配置(超详细的教程)

目录 一、JDK17的安装包下载 二、安装JDK17 第一步&#xff1a;运行JDK的EXE文件 第二步&#xff1a;选择下一步 第三步&#xff1a;选择安装目录 第四步&#xff1a;安装完成 三、配置JDK17的环境变量 第一步&#xff1a;打开系统属性界面 第二步&#xff1a;打开高级…

C#,码海拾贝(31)——约化“对称矩阵“为“对称三对角阵“的“豪斯荷尔德Householder变换法“之C#源代码,《C#数值计算算法编程》源代码升级改进版

using System; namespace Zhou.CSharp.Algorithm { /// <summary> /// 矩阵类 /// 作者&#xff1a;周长发 /// 改进&#xff1a;深度混淆 /// https://blog.csdn.net/beijinghorn /// </summary> public partial class Matrix {…

数据库信息速递 数据库基础设施已经不在青睐 ETL (译)

开头还是介绍一下群&#xff0c;如果感兴趣polardb ,mongodb ,mysql ,postgresql ,redis 等有问题&#xff0c;有需求都可以加群群内有各大数据库行业大咖&#xff0c;CTO&#xff0c;可以解决你的问题。加群请联系 liuaustin3 &#xff0c;在新加的朋友会分到2群&#xff08;共…

RabbitMq的高级特性--RabbitMQ高级特性_消息存活时间

RabbitMQ高级特性_消费端限流 &#xff0c; [解耦&#xff0c; 限流&#xff0c;降低压力&#xff0c;发送消息] 通过消费端限流的 方式限制消息的拉取速度&#xff0c;达到保护消费端的目的。 下面我们新建springboot项目进行测试&#xff1a; 新建项目myproducer 依赖&am…

chatgpt赋能python:Python关联算法:从数据挖掘到推荐系统

Python关联算法&#xff1a;从数据挖掘到推荐系统 Python编程语言已经成为各行各业中数据科学家和工程师的首选语言&#xff0c;其中包括处理数据集合的关联算法。 什么是关联算法&#xff1f; 数学上&#xff0c;关联算法是指在大型和复杂数据集合中&#xff0c; 寻找数据之…

干货 | 携程10个有效降低客户端超时的方法

作者简介 Wen&#xff0c;携程资深后端开发工程师&#xff0c;专注系统性能、稳定性、交易系统等领域。 一、背景 在现今的信息时代&#xff0c;微服务技术已成为一种重要的解决方案&#xff0c;微服务技术可以使系统的规模和功能变的更加灵活&#xff0c;从而获得更高的可扩展…

docker基本命令学习 | Docker网络、Docker镜像发布

&#x1f497;wei_shuo的个人主页 &#x1f4ab;wei_shuo的学习社区 &#x1f310;Hello World &#xff01; docker安装、卸载 docker安装使用 卸载旧版本docker或者环境 [rootiZf8zdcobr3fw7vn0p3538Z /]# yum remove docker \ > docker-client \ >…

打破逢节降价桎梏!海尔智家:满足用户,全网第一

又是一年618&#xff0c;每到这个上半年最重要的消费节点&#xff0c;许多品牌卖家纷纷掀起价格战。 他们使出满减、满赠、满返等五花八门的策略&#xff0c;为了压制对手进行冲量&#xff0c;这也一度让“逢节降价”成为主流。 在市场天平偏向卖家的时代&#xff0c;这些策略…

SVN服务端visualsvn5.1.4下载安装(windows环境)(实操)

Apache Subversion 通常被缩写成 SVN&#xff0c;是一个开放源代码的版本控制系统&#xff0c;Subversion 在 2000 年由 CollabNet Inc 开发&#xff0c;现在发展成为 Apache 软件基金会的一个项目&#xff0c;同样是一个丰富的开发者和用户社区的一部分。 SVN相对于的RCS、CVS…

做自己喜欢的事

这两天沸沸扬扬的消息说稚辉君公司拿到了百度投资 稚晖君刚拿了百度投资&#xff0c;估值被曝已超独角兽 然后昨晚上小孩发烧&#xff0c;我陪床不敢死睡&#xff0c;跟大佬聊了下拿到投资的感受。 然后说到搞技术好玩这个事情&#xff0c;我就跟他分享了我前天到经历 我前天到…

Jenkins概念及安装配置教程(二)

如何安装Jenkins&#xff1f; Jenkins 安装程序也可以作为通用 Java 包 (.war) 使用。如果您将 Jenkins 与 Selenium 一起用于执行跨浏览器测试&#xff0c;我们建议使用 .war 文件&#xff0c;因为您可以通过在非无头模式下在浏览器上执行的自动化测试来见证测试场景的执行。…

【C# 10 和 .NET 6】使用MVC模式构建网站(笔记1)

Building Websites Using the Model-View-Controller Pattern 使用模型-视图-控制器模式构建网站 本章介绍使用 Microsoft ASP.NET Core MVC 在服务器端构建具有现代 HTTP 架构的网站&#xff0c;包括构成 ASP.NET Core MVC 项目的启动配置、身份验证、授权、路由、请求和响应管…

如何设计和使用文档模板 | 技术写作什么鬼

今天看到叶伟民老师的一篇文章&#xff0c;瞬间泪目&#xff1a;叶老师&#xff0c;您是懂人性的啊。在我整天鞭策自己“不能再拖了”的关键时刻&#xff0c;及时分享经验&#xff1a; 是的&#xff0c;这篇文章实在是拖了太久&#xff0c;了太久&#xff0c;太久&#xff0c;久…

web前端 --- javascript(03) -- 函数、内置对象

函数&#xff08;function&#xff09; 具有名称的&#xff0c;为了实现特定功能的代码集合体 &#xff08;1&#xff09;javascript如何定义函数&#xff1a;function关键字定义 function 函数名称 &#xff08;[ 参数列表 ]&#xff09;{ // 函数体 // [return 返回值]…

【Springboot】发送QQ邮件

系列文章目录 文章目录 系列文章目录前言添加Maven依赖QQ邮箱开启POP服务配置application.properties文件Controller层编写 vue前端&#xff08;也可以直接省略&#xff09; 前言 这篇博客用于简单实现SpringBoot中使用Controller发送邮件请求&#xff0c;用户可以收到邮件。 …

Python读写access数据库的实战代码

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…