JavaEE初阶 - 进程与线程

进程与线程

1. 操作系统
2. 操作系统的功能
3. 什么是进程?
4. 操作系统对进程的管理
5. 进程控制块(PCB)的属性
6. 并行和并发
7. 进程间通信
8. 什么是线程?
9. 引入多线程
面试重点:进程和线程的区别与联系


进程与线程

1. 操作系统

  操作系统是一组做计算机资源管理的软件的统称, 目前常见的操作系统有:Windows系列, Linux系列, iOS系列等.

2. 操作系统的功能

操作系统主要有两个功能:
  1. 防止硬件被应用程序滥用
  2. 向应用程序提供简单一致的机制来控制复杂的低级硬件设备

通俗地讲, 操作系统需要同时管理计算机的硬件和软件, 因此, 操作系统在计算机内部的地位大概是这样的:
JavaEE初阶 - 进程与线程_第1张图片
操作系统中提供的功能是非常复杂的, 我们暂时重点认识一下操作系统对"进程"这个软件资源的管理.

3. 什么是进程?

  1. 每个应用程序运行在操作系统之上时, 操作系统会提供一种抽象, 好像系统上只有这个程序在运行, 所有的硬件资源都在被这个程序所使用, 这种假象是通过抽象了一个进程的抽象来完成的, 换言之, 对于操作系统来说, 可执行程序(.exe文件)的一次执行过程, 就是一个进程.

  2. 进程是操作系统进行资源分配的基本单位.
  3. 运行一次可执行文件, 操作系统就会创建一个对应的进程, 关闭一个可执行文件时, 操作系统就会销毁其对应的进程.

4. 操作系统对进程的管理

  1. 先描述一个进程(明确出一个进程上面的一些相关属性)

  这里的描述使用的就是C语言中的结构体(操作系统主要是通过C/C++来实现的), 操作系统中描述进程的结构体, 我们称之为PCB(process control block), 也就是进程控制块.

  1. 再组织若干个进程(使用一些数据结构, 把很多描述进程的信息放在一起, 方便进行增删改查)

    以Linux操作系统为例, 组织进程最典型的实现就是通过双向链表将每个进程的PCB连接起来,

    “创建线程”, 就是先创建出PCB, 然后将PCB连接到双向链表中
    "销毁进程"就是找到链表上该进程的PCB并删除
    "查看任务管理器"就是遍历链表

要想进程正常工作, 操作系统就需要为进程分配一些系统资源:内存, 硬盘, CPU

5. 进程控制块(PCB)的属性

  1. pid(进程id):进程的唯一身份标识(类似于你的身份证号)
  2. 内存指针:指明了这个进程要执行的指令的地址, 以及这个进程执行中依赖的数据的地址
  3. 文件描述符表:程序运行过程中, 经常要和硬盘上的文件进行交互, 进程每打开一个文件, 就会在文件描述符表上新增一项(文件描述符表可以看做一个数组, 数组中的每个元素都是一个结构体, 每个结构体都对应一个文件的相关信息). 一个进程一旦启动, 不论代码中是否实现了开启/关闭文件的代码, 都会默认地打开三个文件, 标准输入(System.in): 标准输出(System.out), 标准错误(System.err)

在讲剩下的属性之前, 我们先引入一个概念:什么是进程调度?

  现在的CPU一般有6核, 8核, 16核等, 如果一个CPU核心管理一个进程, 那么一台计算机能同时运行的进程数就十分有限, 如何让少量的CPU核心同时运行多个(几十个, 甚至上百个)进程, 并且这些进程都能被分配到合适的资源, 这件事情就叫做"进程调度".

上面的属性都是PCB的基础属性, 下面的属性主要是为了实现进程调度.

  1. 状态:这个状态描述了当前这个进程应该怎样被调度.
    就绪态:这个进程已准备就绪, 随时可以在CPU上运行
    阻塞态/睡眠状态:暂时不可以在CPU上运行
    Linux中还存在许多其他的运行状态
  2. 优先级:为不同的进程设置不同的优先级, 先为谁分配时间, 后为谁分配时间, 以及给哪个进程分配的时间多, 哪个进程分配的时间少…
  3. 记账信息:记录了当前的进程已经被执行了多久, 执行了哪些指令, 已经等待了多长时间等等, 记账信息的作用就是为进程调度提供指导依据.
  4. 上下文:表示了该进程上次被调度处CPU时, 当时的运行状态, 之后在CPU上执行时, 可以直接恢复到上次运行时的状态
    进程被调度出CPU之前, 需要把CPU寄存器中所有的内容都保存到PCB的上下文字段中, 下次运行时, 直接从内存中将这些数据再读取到寄存器中即可.
    通俗一点, 就是存档+读档.

6. 并行和并发

并行:多个CPU核心, 同时执行多个进程(举例:我在写博客, 我舍友正在浏览不良网站, 那么我和我舍友之间就是并行的)

并发:一个CPU核心, 先执行进程A, 再执行进程B, 再执行进程C, 再执行进程A…虽然这些进程是按次序被执行的, 但切换的速度非常快, 一次运行的时间非常短, 以至于看起来就像是在同时执行三个进程一样, 这就是并发

注意:并行和并发的区别仅限于微观层面, 在宏观层面上我们无法看到这两个的区别, 并且, 只有在研究操作系统时, 我们才会对并行和并发有所区分, 在其他场景中, 并行和并发统称为并发.

7. 进程间通信

操作系统中通过虚拟地址空间来存储进程, 这样做的好处是将进程并联起来, 一旦一个进程出现bug或者出现崩溃, 也不会影响到其他进程, 确保了进程间的独立性.

但是, 在实际工作中, 我们往往需要在多个进程之间进行交互, 但进程都被虚拟地址空间隔离起来了, 我们如何实现进程间的交互(也就是进程间通信)呢?

操作系统为进程间通信提供了专门的公共空间, 现在最常用的进程间通信方式有两种, 分别为:

  1. 文件操作
  2. 网络操作(socket)

这两种我们后面都会学到.

8. 什么是线程?

  线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位. 如果把进程看作一个工厂, 那么线程就是工厂中的流水线.

9. 引入多线程

现如今, 操作系统已支持多任务, 而程序员也需要并发编程, 其实我们通过多进程完全可以实现并发编程, 但存在着一定的问题:
  1. 如果需要频繁的创建/销毁进程, 这件事的成本非常高(创建和销毁进程需要分配和释放资源, 对于资源的申请和释放是一个非常低效的操作)
  2. 相对于线程来说, 一个进程的调度非常地消耗资源

如何解决这样的问题?

方法一:创建一个进程池
  通过创建进程池可以大幅度地提高效率, 但问题是, 进程池里的闲置进程在不使用时也会消耗内存资源, 这样对内存的消耗非常大.

方法二:通过多线程来实现并发编程
  线程比进程更轻量(一个进程包含一个或多个线程), 每个进程可以执行一个任务, 每个线程也可以执行一个任务, 也能实现并发编程. 并且, 创建线程, 销毁线程, 调度线程的成本要比进程低得多.

为什么进程比线程更轻量?

  进程的, 是重在资源的申请和释放, 进程对资源的申请和释放就好比在一个庞大的仓库中找一个自己想要的东西, 效率非常低下, 而线程是包含在进程中的, 由于一个进程中的多个线程共用一份资源, 因此只有在创建第一个线程时需要分配资源, 后续这个进程中创建线程时就不需要再分配资源了, 大大节约了成本.

既然这样, 是不是线程越多就越好呢?

  当然不是, 如果线程多了, 这些线程可能要竞争同一个资源, 这时整体的速度就受到了限制.

面试重点:进程和线程的区别与联系

  1. 进程中包含线程, 一个进程中可以有一个线程, 也可以有多个线程

  2. 进程和线程都是为了处理并发编程这样的场景, 但进程频繁创建和释放资源的效率比较低, 相比之下, 线程更轻量, 创建和释放效率更高
    (为什么线程更轻量? 少了资源申请和释放的过程)

  3. 操作系统创建进程, 需要给进程分配资源, 进程是操作系统分配资源的基本单位, 操作系统创建的线程, 是要在CPU上调度执行, 线程是操作系统调度执行的基本单位.

  4. 进程具有独立性, 每个进程具有自己独立的虚拟地址空间, 一个进程崩溃不影响其他进程, 而同一个进程中的多个线程共用一个存储空间, 一个线程崩溃可能会影响其他线程, 甚至可能会导致整个进程崩溃.

你可能感兴趣的:(JavaEE初阶,java-ee)