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

这篇具有很好参考价值的文章主要介绍了【多线程系列-01】深入理解进程、线程和CPU之间的关系。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

多线程系列整体栏目


内容 链接地址
【一】深入理解进程、线程和CPU之间的关系 https://blog.csdn.net/zhenghuishengq/article/details/131714191
【二】java创建线程的方式到底有几种?(详解) https://blog.csdn.net/zhenghuishengq/article/details/127968166
【三】深入理解java中线程的生命周期,任务调度 https://blog.csdn.net/zhenghuishengq/article/details/131755387

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

1,进程与线程

1.1,进程与线程的关系

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

【多线程系列-01】深入理解进程、线程和CPU之间的关系,多线程,java,多线程,进程,Thread,高并发,进程和线程的关系,CPU

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

【多线程系列-01】深入理解进程、线程和CPU之间的关系,多线程,java,多线程,进程,Thread,高并发,进程和线程的关系,CPU

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

【多线程系列-01】深入理解进程、线程和CPU之间的关系,多线程,java,多线程,进程,Thread,高并发,进程和线程的关系,CPU

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一个独立的子线程,那么这个线程和这个子线程就可以通过匿名管道的方式进行通信

【多线程系列-01】深入理解进程、线程和CPU之间的关系,多线程,java,多线程,进程,Thread,高并发,进程和线程的关系,CPU

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

2.2,信号

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

2.3,消息队列

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

【多线程系列-01】深入理解进程、线程和CPU之间的关系,多线程,java,多线程,进程,Thread,高并发,进程和线程的关系,CPU

2.4,共享内存(重点)

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

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

【多线程系列-01】深入理解进程、线程和CPU之间的关系,多线程,java,多线程,进程,Thread,高并发,进程和线程的关系,CPU

2.5,信号量

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

2.6,套接字

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

【多线程系列-01】深入理解进程、线程和CPU之间的关系,多线程,java,多线程,进程,Thread,高并发,进程和线程的关系,CPU

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

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

【多线程系列-01】深入理解进程、线程和CPU之间的关系,多线程,java,多线程,进程,Thread,高并发,进程和线程的关系,CPU

也可以直接通过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这种创建线程的方式

【多线程系列-01】深入理解进程、线程和CPU之间的关系,多线程,java,多线程,进程,Thread,高并发,进程和线程的关系,CPU

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

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

但是在通过Thread 创建线程的方法中,没有支持callable作为参数的方法,因此就需要将callable转化成runnable,在此就需要借助到一个重要的类 FutureTask ,其类图如下
【多线程系列-01】深入理解进程、线程和CPU之间的关系,多线程,java,多线程,进程,Thread,高并发,进程和线程的关系,CPU

而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();
    }
}

【多线程系列-01】深入理解进程、线程和CPU之间的关系,多线程,java,多线程,进程,Thread,高并发,进程和线程的关系,CPU

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()来作为判断条件,最好不要自定义标志位,以免出现因为阻塞而导致这个标志位的改变没有被快速的检测到。如果是出现死锁的情况,那么该线程不能使用阻塞停止文章来源地址https://www.toymoban.com/news/detail-561862.html

到了这里,关于【多线程系列-01】深入理解进程、线程和CPU之间的关系的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

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

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

相关文章

  • 【从零学习python 】84.深入理解线程和进程

    进程,能够完成多任务,比如在一台电脑上能够同时运行多个QQ。 线程,能够完成多任务,比如一个QQ中的多个聊天窗口。 进程是系统进行资源分配和调度的一个独立单位。 线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。线程

    2024年02月11日
    浏览(38)
  • 【云原生-深入理解 Kubernetes 系列 3】深入理解容器进程的文件系统

    【云原生-深入理解Kubernetes-1】容器的本质是进程 【云原生-深入理解Kubernetes-2】容器 Linux Cgroups 限制 大家好,我是秋意零。 😈 CSDN作者主页 😎 博客主页 👿 简介 👻 普通本科生在读 在校期间参与众多计算机相关比赛,如:🌟 “省赛”、“国赛” ,斩获多项奖项荣誉证书

    2024年02月06日
    浏览(50)
  • 深入理解操作系统中进程与线程的区别及切换机制(上)

    所谓进程,大家可以理解为我们打开的应用程序,如微信、QQ、游戏等,但也有系统应用是我们看不见的,可以打开任务管理器一探究竟,我们写的代码程序在服务器上在不运行的情况下,它就是一个二进制文件,并不是进程! 一个进程可以包含一个或者多个线程,但对于

    2024年02月11日
    浏览(39)
  • 深入理解操作系统中进程与线程的区别及切换机制(下)

    上一篇文章中我们了解了进程的执行方式,包括早期单核处理器上的顺序执行以及引入多任务概念实现的伪并行。我们还探讨了进程的状态模型。进程可以处于就绪、运行、阻塞和结束等不同的状态。 在本篇文章中,我将探讨研究进程的状态模型、控制结构和切换机制。希望

    2024年02月11日
    浏览(43)
  • 【JUC系列-01】深入理解JMM内存模型的底层实现原理

    JUC系列整体栏目 内容 链接地址 【一】深入理解JMM内存模型的底层实现原理 https://zhenghuisheng.blog.csdn.net/article/details/132400429 【二】深入理解CAS底层原理和基本使用 https://blog.csdn.net/zhenghuishengq/article/details/132478786 【三】熟练掌握Atomic原子系列基本使用 https://blog.csdn.net/zhenghuis

    2024年02月12日
    浏览(47)
  • 【netty系列-01】深入理解网络通信基本原理和tcp/ip协议

    Netty系列整体栏目 内容 链接地址 【一】深入理解网络通信基本原理和tcp/ip协议 https://zhenghuisheng.blog.csdn.net/article/details/136359640 【二】深入理解Socket本质和BIO https://zhenghuisheng.blog.csdn.net/article/details/136549478 在最初的网络中,是借鉴于这个OSI七层网络模型,而在实际开发应用中

    2024年03月17日
    浏览(62)
  • 【天衍系列 01】深入理解Flink的 FileSource 组件:实现大规模数据文件处理

    Apache Flink 是一个流式处理框架,被广泛应用于大数据领域的实时数据处理和分析任务中。在 Flink 中,FileSource 是一个重要的组件,用于从文件系统中读取数据并将其转换为 Flink 的数据流。本文将深入探讨 FileSource 的工作原理、用法以及与其他数据源的比较。 FileSource 是 Fli

    2024年02月21日
    浏览(52)
  • 深入理解Java线程

    进程 程序由指令和数据组成,但程序要运行就要将指令加载进CPU以及数据加载进内存,并且在指令运行过程中可能还会用到磁盘、网络等设备。进程就是用来加载指令、管理内存和IO的。当一个程序被运行,从磁盘加载这个程序的代码至内存,就开启了一个进程。进程可以视

    2024年02月12日
    浏览(41)
  • 深入理解Java线程间通信

    合理的使用Java多线程可以更好地利用服务器资源。一般来讲,线程内部有自己私有的线程上下文,互不干扰。但是当我们需要多个线程之间相互协作的时候,就需要我们掌握Java线程的通信方式。本文将介绍Java线程之间的几种通信原理。 在Java中,锁的概念都是基于对象的,

    2024年02月09日
    浏览(48)
  • 深入理解Java多线程编程

            Java的多线程编程在现代应用程序中扮演着重要的角色。它可以提高应用程序的性能、并发处理能力和响应性。然而,多线程编程也带来了一些挑战,如线程安全、死锁和资源竞争等问题。本文将深入探讨Java多线程编程的基本概念和最佳实践。 1. 理解线程和进程

    2024年02月08日
    浏览(54)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包