【多线程系列-01】深入理解进程、线程和CPU之间的关系

news2024/12/23 23:00:22

深入理解进程线程的关系

  • 一,深入理解进程、线程与CPU之间的关系
    • 1,进程与线程
      • 1.1,进程与线程的关系
      • 1.2,在java中进程与线程的关系
    • 2,进程间的通信方式
      • 2.1,管道
      • 2.2,信号
      • 2.3,消息队列
      • 2.4,共享内存(重点)
      • 2.5,信号量
      • 2.6,套接字
    • 3,CPU核心数和线程数的关系
    • 4,上下文切换
    • 5,java中线程
      • 5.1,创建线程的方式
      • 5.2,线程的启动和停止
        • 5.2.1,线程的启动
        • 5.2.2,线程的终止

一,深入理解进程、线程与CPU之间的关系

1,进程与线程

1.1,进程与线程的关系

进程用官方的话来说:进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配的基本单位,是操作系统结构的基础。 我们常说的app,就是一个进程,当一个程序被运行,从磁盘加载这个程序的代码到内存时,这就开启了一个进程。如下图,打开资源管理器就可以看到这些进程的情况,内部包含一些内存,磁盘,cpu操作,io操作等

在这里插入图片描述

线程指的就是:操作系统能够调度的最小单位

在这里插入图片描述

进程可以独立存活,而线程必须依赖于进程,也就是说,一个进程中可以包含多个线程。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-btnZbPDZ-1689264816662)(img/1689053870099.png)]

1.2,在java中进程与线程的关系

在c语言中,一个进程可以没有线程,但是在java语言中,一个进程至少会包含一个进程。java需要依靠虚拟机执行,由于jvm本身就是一个进程,因此可以通过一个简单的方法查看内部线程的相关信息。如下方法查看,在只有一个主线程的main方法中,通过这个 ThreadMXBean 类来获取当前全部线程的信息

/**
 * @Author: zhenghuisheng
 * @Date: 2023/7/11 13:50
 * 单线程总统计
 */
public class ThreadCount {
    public static void main(String[] args) {
        // 获取线程管理bean
        ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
        // 获取线程和线程堆栈信息
        ThreadInfo[] threadInfo = threadMXBean.dumpAllThreads(false, false);
        for (int i = 0; i < threadInfo.length; i++) {
            ThreadInfo ti = threadInfo[i];
            //打印线程id
            System.out.println("线程id为:" + ti.getThreadId() + "线程名称为:" + ti.getThreadName());
        }
    }
}

其打印的日志如下,从而可以发现本以为只执行了一个main线程,打印日志的时候多了5个线程,在jvm内部会默认启动一些引用线程,垃圾回收,监视器,监听器等线程。所以说在java中,一个进程至少会有一个线程

线程id为:6线程名称为:Monitor Ctrl-Break      //监控中断信号的
线程id为:5线程名称为:Attach Listener 		//监听内存dump,类信息统计,获取系统属性等
线程id为:4线程名称为:Signal Dispatcher		//分发处理发送给JVM信号的线程
线程id为:3线程名称为:Finalizer				//调用finalize方法的线程
线程id为:2线程名称为:Reference Handler		//清除Reference线程
线程id为:1线程名称为:main					

2,进程间的通信方式

同一台计算机的通信称为IPC,不同计算机的通信被称为RPC,需要通过网络并遵守共同的协议。进程与进程之间的通信,主要有几下几种方式:管道、信号、消息队列、共享内存、信号量、套接字

2.1,管道

管道又分为匿名管道和命名管道。匿名管道主要是用于父子进程之间的通信,如一个线程fork一个独立的子线程,那么这个线程和这个子线程就可以通过匿名管道的方式进行通信

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WwF3dWdZ-1689264816665)(img/1689060215475.png)]

命名管管道就是在匿名的管道的基础上,允许无亲缘关系的进程之间的通信。

2.2,信号

如上面的main主线程中,可以发现执行完会有一个 Signal Dispatcher 信号分发的线程,主要用于发送信号和接收信号,类似于一种软件层面的中断,与处理器的中断效果一致,可以通知另外一个进程即将有某个事件要发生

2.3,消息队列

这个消息队列和rabbitMq一样,就是在内存中的一个链表。通过这种方式克服了前面两种通信方式种信号量有限的缺点。就是一边负责将这个信号放到消息队列中,一边负责去接收这个信号量

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nZkzHzKh-1689264816668)(img/1689061371668.png)]

2.4,共享内存(重点)

共享内存这种方式可以说是最有用的进程间的通信方式,就是开辟一个共享空间,每个进程都可以访问和修改这块内存空间,不同的进程可以及时的看到对方进程中对共享数据的更新。这种方式需要依赖互斥锁或者信号量的操作,从而解决可能出现的并发问题。

如下有着三个进程abc,都可以同时的操作上面公共的物理内存空间,从而实现共享内存的方式

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ie9A5NFh-1689264816669)(img/1689061549673.png)]

2.5,信号量

主要是通过这个clh同步等待队列实现,主要是作为进程之间以及同一个进程不同线程之间的同步和互斥手段

2.6,套接字

就是一个socket的套接字,一般用于网络中不同机器之间的进程间的通信,其应用相对广泛。并且在同一台机器中,可以使用 Unix domain socket ,这种方式不需要经过网络协议栈,不需要打包拆包,计算检验和,维护序号和应答等,比纯粹基于网络间的进程通信效率更高。如典型的mysql,不管是从控制台还是从其他机器连接,由于其内部使用的是套接字的连接方式,其实现方面和这个网络的实现方式是一样的,因此其内部和本机可以共用一套代码。套接字的类型主要分有:流套接字、数据报套接字、原始套接字

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ikKUhYQe-1689264816670)(img/1689063439290.png)]

3,CPU核心数和线程数的关系

每个进程都要使用共享的cpu,目前主流的CPU都是多核的,线程是CPU调度的最小单位,也就是说,一个核心CPU只能运行一个线程,但Intel引入这个超线程技术之后,产生了逻辑处理器的概念,使得一个cpu中,可以执行两个线程,从而让cpu数和线程数达到 1:2 的关系 。如下图中,可以查看得到内核是2,逻辑处理器是4

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hqsUNJIk-1689264816673)(img/1689078247767.png)]

也可以直接通过java代码来查看当前机器的cpu个数,显示的结果就是对应的逻辑处理器的个数

//获取的结果是4,而不是2
Runtime.getRuntime().availableProcessors();

在使用线程池时,可以根据判断是io密集型还是cpu密集型,从而来设置这个最大核心数的数量。一般io密集型的最大核心线程数为 2N ,cpu密集型的最大核心线程数为 N或者 N+ 1,这里的N指的就是逻辑处理器的个数

//获取当前机器的逻辑处理器的个数
int n = Runtime.getRuntime().availableProcessors();
//创建io密集型线程池
new ThreadPoolExecutor(
        n*2 - 2,n*2,5L,TimeUnit.SECONDS,new LinkedBlockingDeque<>()
);
//创建cpu密集型线程池
new ThreadPoolExecutor(
        n - 1,n,5L,TimeUnit.SECONDS,new LinkedBlockingDeque<>()
);

4,上下文切换

操作系统要在多个进程之间进行任务调度,而每个线程在使用CPU时总是要使用CPU中的资源,因此操作系统为了保证线程在调度前后的正常执行,就需要一个上下文切换的操作,指的是从一个进程切或线程切换到另一个进程或者线程的切换

cpu内部还包含一些缓存,寄存器,程序计数器等,当cpu从一个线程切换到某另一个线程时,这些属性存放的数据对应的就是该线程内部的数据。而之前线程的数据,如果该线程任务还没有执行完的话,则会通过CPU寄存器或者程序计数器 保存起来,在下一次切换到该线程时,则只需要从结束的那一刻继续往下执行即可。如在jvm的程序计数器中,会保存对应线程的字节码指令,在cpu轮询切换到该线程时,则直接从对应的行数开始执行

上下文切换可以更为详细的描述为内核态对cpu上的进程或者线程的活动:暂停当前进程或者线程的执行,并将此时的CPU状态存储,然后获取一个新的进程或者线程的上下文,在CPU寄存器中恢复

引发cpu上下文切换的原因,主要有:进程、线程切换,线程调度等

5,java中线程

5.1,创建线程的方式

在java的官方jdk中提到,创建线程主要有两种方式: There are two ways to create a new thread of execution. One is to declare a class to be a subclass of Thread . The other way to create a thread is to declare a class that implements the Runnable interface.

就是说:创建线程的方式有两种,一种是继承Thread,一种是实现Runnable接口

public class ThreadTest extends Thread {
    @Override
    public void run() {
    }
}

通过实现runnable的方式创建线程,随后将这个实例作为new Thread的参数

public class ThreadCreate implements Runnable {
    @Override
    public void run() {
    }
}
public class Main(){
    public static void mian(String args[]){
        ThreadCreate threadCreate = new ThreadCreate();
        Thread thread = new Thread(threadCreate);
    }
}

上面两种方式都需要通过Thread类来创建线程,但是这些Thread方法都是void类型,也就是说不会将值返回,因此出现了callable这种创建线程的方式

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-w7pbFcX9-1689264816674)(img/1689255296762.png)]

通过实现callable来获取返回值的方式如下

public class ThreadCreate1 implements Callable {
    @Override
    public Object call() throws Exception {
        return null;
    }
}

但是在通过Thread 创建线程的方法中,没有支持callable作为参数的方法,因此就需要将callable转化成runnable,在此就需要借助到一个重要的类 FutureTask ,其类图如下
在这里插入图片描述

而futureTask中的参数正好是这个callable,因此可以通过包装的形式,将这个callable转换成Runnable

public FutureTask(Callable<V> callable) {
    if (callable == null)
        throw new NullPointerException();
    this.callable = callable;
    this.state = NEW;       // ensure visibility of callable
}

将callable转换成Runnable的方式如下,将这个FutureTask向上转型成Runnable,随后将这个Runnable作为Thread的参数,这样子就可以创建出线程了,同时有返回值也可以通过这个 futureTask.get() 获取到任务的返回值

public class ThreadCreate1 implements Callable {
    @Override
    public void run() {
    }
}
public class Main(){
    public static void mian(String args[]){
        ThreadCreate1 threadCreate1 = new ThreadCreate1();
        //向上转型
        //Runnable futureTask = new FutureTask(threadCreate1);
        FutureTask futureTask = new FutureTask(threadCreate1);
        Thread thread = new Thread(futureTask);
        //获取任务返回的结果
        futureTask.get();
    }
}

5.2,线程的启动和停止

5.2.1,线程的启动

线程的启动比较简单,就是在创建线程之后,调用start方法就可以启动,start()方法让一个线程进入就绪队列等待分配 cpu,分到 cpu 后才调用实现 的 run()方法,start()方法不能重复调用,如果重复调用会抛出异常

ThreadCreate threadCreate = new ThreadCreate();
Thread thread = new Thread(threadCreate);
thread.start();

由于一个Thread在java中只是一个对象,在调用start之后,会调用一个本地方法

private native void start0();

也就是说一个对象会对应本地的一个线程,所以一个线程不能同时start两次,如果两次则会抛出异常

thread.start();
//抛出异常
thread.start();

而run方法在java中,其实也只一个普通的方法,也可以直接调用,但是通过打印出来的线程可以发现执行run方法的线程不是当前线程,而是main方法主线程

public class ThreadCreate implements Runnable {
    @Override
    public void run() {
        System.out.println("当前线程名称为" + Thread.currentThread().getName());
    }
}
public class Main(){
    public static void mian(String args[]){
        ThreadCreate threadCreate = new ThreadCreate();
        //直接执行run方法
        thread.run();
    }
}

在这里插入图片描述

5.2.2,线程的终止

线程的终止分为多种情况:一种是自然终止,如run方法中把整个任务给执行了,或者说是代码出现异常的时候也会终止;一种是中断 出现的停止,如调用这个Thread.interrupt() 方法。

自然终止也可以是手动的调用一些如 stop()suspend()resume() 等方法,但是这些方法上面都有一个 Deprecated 过期的注解,表示已经被废弃。主要是因为这些方法虽然可以让操作系统进行一个停止或者挂起的操作,但是这个过程中这些被挂起或者停止的线程并不会去释放资源。如stop方法是一个强力的结束线程的方法,但是就是因为力度太大,导致不会去释放cpu等其他资源,或者造成文件损坏;suspend是一个线程挂起的方法,被挂起阶段也不会释放资源,如一些锁,文件等,就会出现死锁等问题。

因此为了更优雅的停止线程并且可以释放资源,则引入了这个 interrupted 方法。如thread1线程通知thread2线程马上要中断了,那么thread2接收到这个信号后会先判断一个标志位是否为true,如果为true则先进行一个释放资源的操作,再进行中断

public static boolean interrupted() {
    //检查标志位是否为true
    return currentThread().isInterrupted(true);
}

interrupt 是用来中断操作,interrupted 方法用来判断是否需要中断,也就是说也可以不中断,即不理会这个中断请求,理会了就一定会先释放资源,再进行一个挂起或者停止的工作。

//设置中断的标志位为true
thread.interrupt();

总的来说就是在设置标志位之后,需要通过代码检查这个标志位,标志位默认是false,在调用interrupt方法之后,就会将这个标志位改为true,如果在线程中不去检查这个标志位,那么线程和以前一样执行,如果做一个线程标志位的检查,通过查看这个isInterrupted方法,如果为true,当前线程会先释放资源,再挂起或者之间停止。

public class ThreadCreate implements Runnable {
    @Override
    public void run() {
        Thread thread = Thread.currentThread();
        //用于判断标志位
        while (!thread.isInterrupted()){
            System.out.println("线程没被中断");
        }
    }
}
public class ThreadCount {
    public static void main(String[] args) {
        ThreadCreate threadCreate = new ThreadCreate();
        Thread thread = new Thread(threadCreate);
        //线程中断,将标志位改为true
        thread.interrupt();

    }
}

也就是说,thread.interrupt()thread.isInterrupted() 要结合使用。并且这个标志位最好是通过这个thread.isInterrupted()来作为判断条件,最好不要自定义标志位,以免出现因为阻塞而导致这个标志位的改变没有被快速的检测到。如果是出现死锁的情况,那么该线程不能使用阻塞停止

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

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

相关文章

taro-ui-vue3 的虚拟列表组件VirtualScroll

项目&#xff1a;taro3vue3 用法&#xff1a; <at-virtual-scrollbench"5":height"listHeight":items"fieldList":item-height"itemHeight" ><template #default"{ index, item }"><view :id"merchan…

【力扣算法12】之 11. 盛最多水的容器 python

文章目录 问题描述示例1示例2提示 思路分析代码分析完整代码详细分析运行效果截图调用示例运行结果完结 问题描述 给定一个长度为 n 的整数数组 height 。有n条垂线&#xff0c;第i条线的两个端点是(i, 0)和(i, height[i])。 找出其中的两条线&#xff0c;使得它们与 x 轴共同构…

SQL 优化换汤不换药的时代变了与SQL审核

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

Kubernetes轻量级日志工具Loki安装及踩坑记录

Loki简介 Loki是Grafana出品的一个轻量级日志系统&#xff0c;熟悉ELK的都知道ELK使用起来的成本&#xff0c;而且仅仅是日志检索使用ELK的话有点大材小用了。Loki8技术栈中使用了以下组件。 Promtail 用来将容器日志发送到 Loki 或者 Grafana 服务上的日志收集工具&#xff0c…

谷歌Bard更新中文支持;GPT-4:1.8万亿参数、混合专家模型揭秘; Meta推出商用版本AI模型

&#x1f989; AI新闻 &#x1f680; 谷歌的AI聊天工具Bard更新&#xff0c;增加中文支持 摘要&#xff1a;谷歌的AI聊天工具Bard新增中文环境&#xff0c;用户可以使用简体和繁体中文进行交流。然而&#xff0c;与竞品相比&#xff0c;Bard的回复略显生硬&#xff0c;语义理…

C# Winfrom将DataGridView数据导入Excel

1.项目添加Word和Excel的COM类型库引用 2.创建Excel工作表 //定义Excel操作对象Microsoft.Office.Interop.Excel.Application excelApp new Microsoft.Office.Interop.Excel.Application();//定义Excel工作表Microsoft.Office.Interop.Excel.Worksheet worksheet excelApp.Wo…

【专题速递】在线K歌、云化XR、咔嚓剪辑和FFmpeg直播能力更新计划

// 在线K歌的技术方案选型有哪些&#xff1f;对于沉浸式XR我们又有什么新的思考&#xff1f;高性能低依赖的剪辑视频需要具备什么技术&#xff1f;7月29日LiveVideoStackCon2023上海站客户端体验与性能优化专场&#xff0c;为您解答。 客户端体验与性能优化 客户端作为直接面…

用Python画一个星空

1 问题 如何用Python画一个简单的星空&#xff1f; 2 方法 在Python中有着各种各样的工具包&#xff0c;比如math、pillow、requests等等&#xff0c;每个包有着自己专门的功能。要用python画星空&#xff0c;在绘制星空的过程中一般需要运用到turtle工具&#xff0c;它是属于P…

PVE虚拟化平台之安装RHEL9系统

PVE虚拟化平台之安装RHEL9系统 一、RHEL9介绍1.1 RHEL9简介1.2 RHEL9新功能 二、上传镜像到PVE存储2.1 检查PVE环境2.2 上传镜像 三、创建虚拟机3.1 设置虚拟机名称3.2 操作系统设置3.3 系统设置3.4 磁盘设置3.5 CPU设置3.6 内存设置3.7 网络设置3.8 确定虚拟机配置 四、安装操…

【刷题】在二叉树中分配硬币

在二叉树中分配硬币 https://leetcode.cn/problems/distribute-coins-in-binary-tree/description/ 描述 给定一个有 N 个结点的二叉树的根结点 root&#xff0c;树中的每个结点上都对应有 node.val 枚硬币&#xff0c;并且总共有 N 枚硬币。 在一次移动中&#xff0c;我们…

PLSQL Developer怎样查看当前活动会话

点‘工具’-‘会话’&#xff1a; 选择‘Active sessions’: 点击某个会话&#xff0c;可以看到其对应的sql&#xff1a;

Feign技术

说明&#xff1a;Feign和RestTemplate一样&#xff0c;是用于微服务之间通信的&#xff0c;配合注册中心技术Nacos&#xff0c;可以搭建一个完整的SpringCloud环境。本文介绍在NacosFeign环境下&#xff0c;Feign的使用。 环境介绍&#xff1a;创建两个服务&#xff0c;订单服…

Web3的2048,Sui 8192能否打开全链游戏的大门?

作者&#xff1a;Peng SUN&#xff0c;Foresight News Sui 8192&#xff1a;一局游戏就是一个NFT Sui 8192智能合约基于Move语言编写&#xff0c;构成非常简单&#xff0c;包括游戏、Game Board与排行榜&#xff08;Leaderboard&#xff09;三部分&#xff0c;覆盖方块移动、…

Linux基本知识/Linux文件夹创建、删除、复制等命令怎么用/grep管道符是啥

前情提要&#xff1a;经过一段时间的沉淀&#xff0c;因为要用到Linux&#xff0c;索性就梳理总结一下Linux的基本知识&#xff01; 一、Linux文件目录 1.1 结构 是一个树形结构&#xff0c;只有一个根目录/ 1.2 路径描述 linux系统中&#xff0c;路径层次关系用/来表示w…

IDEA安装JRebel插件激活LS client not configued问题

JRebel插件安装步骤&#xff1a;settings->Plugins中搜索JRebel&#xff0c;然后安装即可 这里安装的是最新版本&#xff0c;安装完后会要求重启IDEA&#xff0c;重启后一般会自动弹出JRebel面板&#xff0c;里面有个Activation&#xff0c;点击后弹出激活页面&#xff0c;我…

java 通过Json -schema完成对数据的效验

Json -schema 1.对象的效验2.数组套对象的效验3. 字符串的效验长度效验(minLength)(maxLength)正则效验日期和时间 4.对象套对象效验5.对象套数组6. 其他参数required(必须要填&#xff09;enum(范围之内&#xff09;not&#xff08;不&#xff09;anyOf 和allOf&#xff08;双…

C++之重写基类虚函数添加override区别(一百六十二)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

5. MySQL - JDBC SQL 注入 博客系统(万字详解)

目录 1. 介绍 2. 使用 JDBC 连接数据库 2.1 如何使用 JDBC 连接数据库 2.2 导入的各个类 2.3 DataSource 对象的创建 2.4 从 DataSource 对象中得到 Connection 对象 2.5 创建 Statement 对象 2.6 从 ResultSet 中遍历每行结果&#xff0c;从每行中获取每列的值 2.7 代…

Django-linux主机计划任务查看服务

目录 需求 功能介绍 页面效果 代码编写 docker部署 需求 线上主机一百台左右&#xff0c;经常会在某个服务器上放置一些自动化脚本&#xff0c;并配置计划任务&#xff0c;时间长可能忘记计划任务所在服务器&#xff0c;所以开发一个用于收集展示crontab任务的服务 语言框…

Coremail易念:2022年企业邮件钓鱼模拟演练分析报告

以下为精华版阅读&#xff0c;如需下载完整版&#xff0c;关注【CACTER邮件安全】&#xff0c;后台回复关键词【钓鱼报告】即可免费下载。 Coremail&易念科技《2022年企业邮件钓鱼模拟演练分析报告》重磅发布&#xff01;有哪些精华亮点&#xff0c;点击下拉。 一、制造业钓…