这是我参与11月更文挑战的第18天,活动详情查看:2021最后一次更文挑战
今天主要来好好介绍一下进程,线程以及多线程这几个概念,为下一次的多线程做好准备。
首先我们要知道的是进程和线程的概念是在建立在操作系统层面上的,那就来捋捋看怎么就出来了进程和线程。
在单 CPU 时代,若是没有操作系统的控制,那么一个程序会一直在 CPU 上执行,但我们不希望这样,所以就设计出 “时间片” 的概念,这个时间片就是由操作系统的调度器来控制,专门负责切分 CPU 的时间片,轮流分给不同的程序。每一个应用程序只在一个时间片内运行,而时间片过于小,所以在宏观上感觉就是多个程序在同时执行。
在单 CPU 时代,还有一个问题,那就是多个程序会共享同一块内存,这会引起很多麻烦,为了解决这个问题,首先想到的就是为不同的程序分配不同的物理内存块。
然而这样做有个很大的问题:每个程序都要协调商量好怎样使用同一个内存上的不同空间,软件系统和硬件系统千差万别,使这种定制的方案没有可行性。为了解决这个麻烦,计算机系统引入了 “虚拟地址” 的概念,从三方面入手来做:
硬件上,CPU 增加了一个专门的模块叫 MMU,负责转换虚拟地址和物理地址。操作系统上,操作系统增加了另一个核心组件:memory management,即内存管理模块,它管理物理内存、虚拟内存相关的一系列事务。
应用程序上,发明了一个叫做【进程】的模型,(注意)每个进程都用【完全一样的】虚拟地址空间(为什么虚拟地址一样,但是实际的物理地址不一样是因为不同的进程有不同的页表,而线程拥有相同的页表,所以物理地址也相同),然后经由操作系统和硬件 MMU 协作,映射到不同的物理地址空间上。不同的【进程】,都有各自独立的物理内存空间,不用一些特殊手段,是无法访问别的进程的物理内存的。
现在,不同的应用程序,可以不关心底层的物理内存分配,也不关心 CPU 的协调共享了。然而还有一个问题存在:有一些程序,想要共享 CPU,【并且还要共享同样的物理内存】,这时候,一个叫【线程】的模型就出现了,它们被包裹在进程里面,在调度器的管理下共享 CPU,拥有同样的虚拟地址空间,同时也共享同一个物理地址空间,然而,它们无法越过包裹自己的进程,去访问另一个进程的物理地址空间。
还有一些比较基本的定义,进程是程序的一次执行,进程是资源(CPU、内存等)分配的基本单位,它是程序执行时的一个实例。线程是 CPU 的基本调度单位,是进程的一个执行流。
搞清楚了进程和线程的关系,那么多线程就很好说了,指的就是一个进程运行时产生了不止一个线程(一个进程最少有一个线程),在 Java 程序中体现在 main 方法的执行是一个进程,我们在 main 方法中通过 Thread 类来创建多个线程进而运行他们,也就达到多线程的目的。