每每聊到线程Thread

这篇具有很好参考价值的文章主要介绍了每每聊到线程Thread。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

每每聊到线程Thread

每每聊到线程Thread

进程和线程

进程

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

线程

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

每每聊到线程Thread

CPU 和 并发

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

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

每每聊到线程Thread

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

每每聊到线程Thread

每每聊到线程Thread

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倍!

每每聊到线程Thread

问题分析

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

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

每每聊到线程Thread

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

每每聊到线程Thread

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

每每聊到线程Thread

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

每每聊到线程Thread

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

每每聊到线程Thread

虚拟线程

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");
}

每每聊到线程Thread

如果你喜欢本文

长按二维码关注

每每聊到线程Thread文章来源地址https://www.toymoban.com/news/detail-468499.html

到了这里,关于每每聊到线程Thread的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 计算机操作系统实验-进程调度模拟算法

    进程调度是处理机管理的核心内容。本实验要求用高级语言编写模拟进程调度程序,以 便加深理解有关进程控制快、进程队列等概念,并体会和了解优先数算法和时间片轮转算法 的具体实施办法。 1.设计进程控制块 PCB 的结构,通常应包括如下信息: 进程名、进程优先数(

    2024年02月05日
    浏览(74)
  • 【Linux】进程的韵律:探索计算机世界中的动态舞台

    进程:一个具有一定功能的程序在一个数据集合上的一次动态执行过程。 进程是指正在运行的程序,它是操作系统进行资源分配和调度的基本单位。在计算机中,每个进程都有自己的地址空间、堆栈、文件描述符、环境变量等,每个进程之间相互独立,互不干扰。 进程可以

    2024年02月01日
    浏览(41)
  • 计算机操作系统【慕课版】习题答案(第2章进程的描述与控制)

    一:简答题 (1).什么是前趋图?试画出下面四条语句的前趋图. S1:a=x+y; S2:b=z+1; S3:c=a-b; S4:w=c+1; 答:前趋图(Precedence Graph)是一个有向无循环图,记为DAG(DirectedAcyclicGraph),用于描述进程之间执行的前后关系。 (2)什么是进程? OS中为什么要引入进程?它会产生什么样的

    2024年04月13日
    浏览(36)
  • 计算机操作系统重点概念整理-第三章 进程同步【期末复习|考研复习】

    计算机操作系统复习系列文章传送门: 第一章 计算机系统概述 第二章 进程管理 第三章 进程同步 第四章 内存管理 第五章 文件管理 第六章 输出输出I/O管理 给大家整理了一下计算机操作系统中的重点概念,以供大家期末复习和考研复习的时候使用。 参考资料是王道的计算

    2024年02月08日
    浏览(55)
  • 计算机操作系统重点概念整理-第二章 进程管理【期末复习|考研复习】

    计算机操作系统复习系列文章传送门: 第一章 计算机系统概述 第二章 进程管理 第三章 进程同步 第四章 内存管理 第五章 文件管理 第六章 输出输出I/O管理 给大家整理了一下计算机操作系统中的重点概念,以供大家期末复习和考研复习的时候使用。 参考资料是王道的计算

    2024年02月08日
    浏览(59)
  • 实现时间片轮转算法(模拟)计算机操作系统实验5:进程调度算法模拟-RR

    实验内容: 实现时间片轮转算法(模拟),要求如下: 1、用到的数据结构 /* PCB / struct PCB { pid_t pid;//进程 PID int state; //状态信息,1 表示正在运行,0 表示暂停,-1 表示结束 unsigned long runned_time;//已运行时间 unsigned long need_running_time;//剩余运行时间 }; / PCB集合 */ struct PCB pcb[TOT

    2024年02月04日
    浏览(56)
  • 计算机视觉---flask框架封装目标检测,应用线程提高程序运行效率

    1.前言 上一篇文章flask部署 目标检测算法中讲到可以将检测算法封装到flask框架中进行web端展示,但在实际应用中发现一些问题并进行了解决,在本文中进行补充。 2.利用线程,提高flask程序运行效率 flask web端访问时,每次都会从头加载程序,导致每次访问页面刷新率很低或

    2024年02月16日
    浏览(48)
  • 【Linux】 由“进程”过渡到“线程” -- 什么是线程(thread)?

    如何看待地址空间和页表: 地址空间是进程能看到的资源窗口 页表决定,进程真正拥有资源的情况(页表映射多少才是拥有多少) 合理的对地址空间+页表进行资源划分,我们就可以对一个进程所有的资源进行分类 虚拟地址如何找到物理地址: 最后一级页表存放的是页框的起

    2024年02月15日
    浏览(44)
  • [嵌入式系统-32]:RT-Thread -17- 任务、进程、线程的区别

    目录 一、基本概念澄清 1.1 任务 1.2 进程 1.3 线程 1.4 比较 1.5 任务VS进程 1.6 进程 VS 线程 1.7 任务 进程 线程 发展历史 任务(Task): 进程(Process): 线程(Thread): 发展趋势: 二、不同操作系统中任务、进程、线程 2.1 Linux:没人任务,只有进程与线程 进程相关函数: 线程

    2024年02月21日
    浏览(45)
  • 计算机组成原理之计算机硬件发展和计算机系统的组成

    学习的最大理由是想摆脱平庸,早一天就多一份人生的精彩;迟一天就多一天平庸的困扰。各位小伙伴,如果您: 想系统/深入学习某技术知识点… 一个人摸索学习很难坚持,想组团高效学习… 想写博客但无从下手,急需写作干货注入能量… 热爱写作,愿意让自己成为更好

    2024年01月24日
    浏览(86)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包