一、操作系统
操作系统是一组做计算机资源管理的软件的统称。目前常见的操作系统有:Windows系列、Unix系列、Linux系列、OSX系列、Android系列、iOS系列、鸿蒙等。
操作系统的定位如下:
操作系统由两个基本功能:
1) 防止硬件被应用程序滥用;
2) 向应用程序提供简单一致的机制来控制复杂而又通常大相径庭的低级硬件设备。
二、进程/任务(Process/Task)
1. 什么是进程/任务
每个应用程序运行于现代操作系统之上时,操作系统会提供一种抽象,好像系统上只有这个程序在运行,所有的硬件资源都被这个程序在使用。这种假象是通过抽象了一个进程的概念来完成的,进程可以说是计算机科学中最重要和最成功的概念之一。
人话:每个应用程序在内存运行时就是一个进程
2. 进程控制块抽象(PCB Process control Block)
计算机内部要管理任何现实事物,都需要将其抽象成一组有关联的、互为一体的数据。在 Java 语言中,我们可以通过类/对象来描述这一特征。
// 以下代码是 Java 代码的伪码形式,重在说明,无法直接运行 class PCB { // 进程的唯一标识 —— pid; // 进程关联的程序信息,例如哪个程序,加载到内存中的区域等 // 分配给该资源使用的各个资源 // 进度调度信息(留待下面讲解) }
我们这里用类去类比PCB,但是操作系统都是用C语言写的,所以应该把PCB描述成一个结构体更加合适
一个PCB一般包含如下信息:
(1)pid:每个进程需要有一个唯一的身份标识~
(2)内存指针:当前这个进程使用的内存是哪一部分
(3)文件描述符表:进程运行的时候,使用了哪些硬盘上的资源
这样,每一个 PCB 对象,就代表着一个实实在在运行着的程序,也就是进程。
操作系统再通过这种数据结构,例如线性表、搜索树等将 PCB 对象组织起来,方便管理时进行增删查改的操作。
3. CPU分配 —— 进程调度(Process Scheduling)
注:进程是计算机分配资源的基本单位!!!(死记)
为了便于讨论和理解,我们大部分的场景下假设是单CPU单核的计算机。
操作系统对CPU资源的分配,采用的是时间模式 —— 不同的进程在不同的时间段(时间片)去使用 CPU 资源。
首先了解什么是并行和并发:
并行:同一时刻,两个核心,同时执行两个进程,此时这两个进程就是并行执行~
并发:一个核心,先执行进程1,执行一会之后,再去执行进程2,在执行一会之后,再去执行进程3,此时只要这里的切换速度足够快,看起来,进程123就是“同时”执行
例如:一个CPU有16个核心,也可以同时执行142个任务!通过并发+并行的方式来完成,这种状态完全是有操作系统只是控制的,程序猿根本感知不到~~,所以很多时候就把并行+并发统称为并发!【并发程度更高了,就可以称为高并发。比如一个核心(主体)并发指向了1w个任务!!,就可以称为“高并发”】
接下来这一组属性,都是描述CPU资源相关的属性。这些属性都是辅助进程调度:
a) 进程状态
简单认为,进程状态主要是这两个:
就绪态:该进程已经准备好了,随时可以上CPU执行
阻塞态:该进程暂时无法上CPU执行
举个栗子:
我同时交三个男朋友,每周要给这三个小哥哥(A、B、C)安排时间表~
默认情况下,这三个小哥哥都是随叫随到,我在排时间就十分灵活,也就是每个人都是就绪态。
假设A和我说,他要出差一个月~~
于是A就相当于阻塞态,B和C就是就绪态
b) 进程的优先级
进程之间的调度不一定是“公平”的,有优先级之分
举个栗子:
A最有钱,B最帅,C最会舔
那么在我眼中,A的优先级最高,可能每周会排4天给A,然后是B,最后才是C。
c) 进程的上下文
上下文,就是描述当前进程执行到哪里(也就是存档记录),为什么会有这个存单记录呢,因为CPU在调度每个进程都是随机的,由时间片决定。
进程在离开CPU的时候就要把当前运行的中间结果“存档”,等下次进程再次被CPU调度的时候,再回复之前的“存档”,从上次的结果继续往后指向~~【如果进程结束了,就不必村了,如果是暂时离开,就得存!】
举个栗子:
有一天,我和A在一起,A给我说:“下个月,我带你去南澳岛玩玩,你准备准备。”此时,我就可以准备一套性感的内衣。
第二天,我和B在一起,B给我说:“下个月,他的妈妈要过生日,他想让我帮忙挑选个礼物,也让我准备准备。”我准备给他妈妈挑选个手机。
过了一段时间之后,此时,A问我准备得咋样,我说手机买好了。B问我准备得咋样,我说内衣买好了,这就尴尬了~~
为了避免这种情况,我就需要在时间安排表上记录一下:和他们做了什么时,保证下次和他们在一次不会出差错!
具体到进程,所谓的上下文具体指就是进程的运行过程中,CPU内部的一系列寄存器的值
d) 进程的记账信息
统计了每个进程,在CPU上执行了多久了,可以作为调度的参考依据,可能会给一个执行时间少的进程增加他的调度次数
比如,按照之前的优先级,每周只给C排一天的实践~~
过了几个月之后,我就发现C对我的态度逐渐冷淡了,舔得力不从心
此时排查之前的时间表,原来排给C的时间太少了,难怪感情就淡了~
接下来的实践里给C多排点时间,多给他点甜头。
4. 内存分配 —— 内存管理(Memory Manage)
操作系统对内存资源的分配,采用的是空间模式 —— 不同进程使用内存中的不同区域,互相之间不会干扰.
操作系统给进程分配的内存,是以“虚拟地址空间”的方式进行分配的,每个进程范围的内存地址,都不是真实的物理地址。
人话:页表其实就相当于一个检测员,看看你的给的地址是否正确。保证了一个进程的独立性,一个进程无法直接干预另一个进程的内存内容,每个进程都有自己的独立的地址空间。大大提升了操作系统的“稳定性”。
5. 进程间通信(Inter Process Communication)
如上所述,进程是操作系统进行资源分配的最小单位,这意味着各个进程互相之间是无法感受到对方存在的,这就是操作系统抽象出进程这一概念的初衷,这样便带来了进程之间互相具备“隔离性
(Isolation)”但现代的应用,要完成一个复杂的业务需求,往往无法通过一个进程独立完成,总是需要进程和进程进行配合地达到应用的目的,如此,进程之间就需要有进行“信息交换“的需求。进程间通信的需求就应运而生。
目前,主流操作系统提供的进程通信机制有如:管道、共享内存、文件、网络、信号量、信号
其中,网络是一种相对特殊的 IPC 机制,它除了支持同主机两个进程间通信,还支持同一网络内部非同一主机上的进程间进行通信。
举个栗子:
一个小区因为疫情封了,外面的人进不来,里面的人出不去。
我想点外卖,外卖员就会把外卖送到保安亭(相当于公共空间)
外卖员是一个进程,我是一个进程!
三、线程(Thread)
前面讲了那么多进程的知识,目的不在讲进程,而是为了引出线程!!!
1. 什么是线程
首先,我们得知道进程,是比较“重量的”,速度慢,消耗资源多的
创建一个进程,成本比较高
销毁一个进程,成本也比较高
调度一个进程,成本也挺高的……
所以多进程编程,可以解决并发的问题,但不是一个高效的选择!
为什么进程这么有“重量”?
主要体现在资源分配上,资源分配是一个耗时的操作(包括了内存分配,文件读取分配等等)
引入线程:
举个栗子:
我的大姨想要扩建自己的厂子,有两种方案
(1)再去找个地皮,重新建立物流系统等等(多进程)
(2)在原本的厂子上进行扩建,不需要重新建立物流系统,做到了资源共享,同时提高了出货量(多线程)
我们再次回顾下进程调度:
(1)为啥要调度?狼多肉少(进程多,CPU处理不过来)
(2)CPU按照并发的方式来执行进程的
(3)PCB中提供了一些属性,进程的优先级、进程的状态、进程的上下文、进程的记账信息……
2. 线程和进程的关系
进程包含线程,一个进程里可以有一个线程,或者多个线程。每个线程都是一个独立的执行流。多个线程直接,也是并发执行的。
一个进程中的多个线程之间,共用同一份系统资源,包含了①内存空间、②文件描述符表
只有在进程启动,创建第一个线程的时候,需要花成败去申请系统资源,一旦进程(第一个线程)创建完毕,此时,后续再创建的线程,就不必再申请资源了,创建/销毁的效率也提高了不少。(线程也被称为“轻量级”的进程)
总结进程和线程的区别:
- 进程包含线程。
- 进程有自己独立的内存空间和文件描述符表。同一个进程中的多个线程之间共享同一份地址空间和文件描述符表。
- 进程是操作系统资源分配的基本单位,线程是操作系统调度执行的基本单位
- 进程之间具有独立性,一个进程挂了,不会影响到别的进程;同一个进程里的多个线程之间,一个线程挂了,可能会把整个进程带走,影响到其他线程的。
四、问题提问和解答
(1)计算机在什么情况下会再创建一个进程
答:取决于你的代码咋写。创建进程和创建线程都是程序猿可以控制的~~。例如同一个程序,内部想要并发地完成多组任务,此时使用多线程比较合适(比如:微信,是一个程序,里面同时聊天、看朋友圈等等可以理解为线程)。多个程序之间,此时就是多个进程了(比如:微信、QQ音乐等等)。
(2)进程里的多线程有没有上限呢?
答:只要系统资源够,没有上限的。但也不是线程越多越好。
五、各个技术面试的多频考点
(1)谈到JavaSE,最高频的问题是多态
(2)谈到数据结构,最高频的问题是哈希表的实现
(3)谈到数据库,最高频的问题是索引和事务
(4)谈到系统编程,最高频的问题是进程和线程的基本概念和区别