其实可以写的更好的,等论文忙完了、工作着落了后面来仔细琢磨修改。
其实在回答这些高频面试题目的时候回答都是千篇一律,我也是这其中之一,总是给面试官没有啥突出的亮点,反复考虑后还是在自己知道表面的基础上继续深入了解每个知识点的底层原理。
1.进程、线程及协程的区别
提到线程和进程,必须先了解一些预备知识。
程序: 程序是为实现特定目标或解决特定问题而用计算机语言编写的命令序列的有序集合。
我们代码要执行,需要使用计算机的一些资源,计算资源和存储资源,也就是电脑的 CPU 和 内存(RAM),这两样东西是我们程序运行不可或缺的。在执行程序时,需要向计算机申请。
CPU和调度器:
在单核计算机里,CPU 是无法被多个程序并行使用的,也就是同一时间,只有一个程序能占用 CPU。没有操作系统的情况下,一个程序一直独占着 CPU,直到它运行结束。为了让多个程序执行在一个CPU上运行,就引出了进程调度。它关心的只是怎样把单个 CPU 的运行拆分成一段一段的“运行片”,轮流分给不同的程序去使用,而在宏观上,因为分配切换的速度极快,就制造出多程序并行在一个 CPU 上的假象。
内存和虚拟空间:
具体的内存分配过程是这样的:每个程序在启动和执行时,会被分配到一块虚拟内存空间。这个虚拟地址空间是“完全一样”的,经由操作系统和硬件 MMU 协作,会映射到不同的物理地址空间上。下面引出上面总结。
为了更好的给程序执行分配空间所以需要一个内存分配策略-虚拟内存空间:它分配给程序“一个连续完整的地址空间”,但实际上这个地址空间是被分隔成多个物理内存碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换。在硬件上,CPU 增加了一个专门的模块叫 MMU(Memory Management Unit),负责转换虚拟地址和物理地址。
总结上面的内容,就是通过调度器和虚拟内存,每个程序启动和执行都可以获取到所需要的内存和 CPU。
人们把上面这句话中的“程序启动和执行”概括成了一个词,叫做进程。进程就表示程序的执行过程,所以上面那句话可以这样说:通过调度器和虚拟内存,每个进程都可以获取到所需要的内存和 CPU。
但是又出现了一个问题:在程序执行的时候,怎么尽可能高效地利用系统分配给它的内存?怎样才能利用进程的空闲内存呢?这个问题的本质就是如何共享相同的内存引出线程。
线程和进程的区别:
1.从底层来说,进程是程序的一次执行过程,进程=cpu加载上下文+cpu执行+cpu保存上下文,线程=(在共享进程的上下文的基础下,将程序的实现分为多个程序段来执行,每个程序段就被称为线程),也就是说线程和进程都是cpu工作时间段的描述,只不过线程的cpu工作时间段小,进程的cpu工作时间段大。
2.在同一进程中的不同线程共享相同的地址空间,而不同的进程则在内存中有独立的地址空间。因此线程可以读写同样的数据结构和变量,便于线程之间的通信。相反,进程间通信(IPC)很困难且消耗更多资源。
3.一个线程死掉就等于整个进程死掉,而一个进程死掉,由于保护机制,其他进程不受影响,所以多进程的程序要比多线程的程序健壮(稳定)。
4.一个进程可包含多个线程,线程属于进程。
5.线程是调度(获得的cpu的所有权)的基本单位,进程是系统资源分配的基本单位。
协程:
协程是一种用户态的轻量级线程,在应用层,由程序控制,协程的调度完全由用户控制。协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快
进程与程序的区别:
程序是指令和数据的有序集合,其本身没有任何运动的含义,是一个静态的概念,而进程则是在处理机上的一次执行过程,它是一个动态的概念。
进程的执行离不开程序,在给进程分配的虚拟地址空间中,有一个叫做代码区,存放着程序的代码。
注:分配给每个进程的内存可以分成 4 个区:栈区、堆区、数据区和代码区。
栈区:主要用来存放局部变量, 传递参数, 存放函数的返回地址;
堆区:用于存放动态分配的对象, 当使用 malloc 和 new 等进行分配时,所得到的空间就在堆中。动态分配得到的内存区域附带有分配信息, 所以可以 free 和 delete 它们;
数据区:全局,静态和常量是分配在数据区中的,数据区包括 bss(未初始化数据区)和初始化数据区。
进程三种基本状态:
- 就绪状态:进程已获得除CPU外的所有必要资源,只等待CPU时的状态。一个系统会将多个处于就绪状态的进程排成一个就绪队列。
- 执行状态:进程已获CPU,正在执行。单处理机系统中,处于执行状态的进程只一个;多处理机系统中,有多个处于执行状态的进程。
- 阻塞状态:正在执行的进程由于某种原因而暂时无法继续执行,便放弃处理机而处于暂停状态,即进程执行受阻。(这种状态又称等待状态或封锁状)。
6种线程状态以及如何转换:
Java线程的生命周期中,存在几种状态。在Thread类里有一个枚举类型State,定义了线程的几种状态,分别有:
- NEW: 线程创建之后,但是还没有启动(not yet started)。这时候它的状态就是NEW
- RUNNABLE: 正在Java虚拟机下跑任务的线程的状态。在RUNNABLE状态下的线程可能会处于等待状态, 因为它正在等待一些系统资源的释放,比如IO
- BLOCKED: 阻塞状态,等待锁的释放,比如线程A进入了一个synchronized方法,线程B也想进入这个方法,但是这个方法的锁已经被线程A获取了,这个时候线程B就处于BLOCKED状态(比较常见)
- WAITING: 等待状态,处于等待状态的线程是由于执行了3个方法中的任意方法。 1. Object的wait方法,并且没有使用timeout参数; 2. Thread的join方法,没有使用timeout参数 3. LockSupport的park方法。 处于waiting状态的线程会等待另外一个线程处理特殊的行为。 再举个例子,如果一个线程调用了一个对象的wait方法,那么这个线程就会处于waiting状态直到另外一个线程调用这个对象的notify或者notifyAll方法后才会解除这个状态
- TIMED_WAITING: 有等待时间的等待状态,比如调用了以下几个方法中的任意方法,并且指定了等待时间,线程就会处于这个状态。 1. Thread.sleep方法 2. Object的wait方法,带有时间 3. Thread.join方法,带有时间 4. LockSupport的parkNanos方法,带有时间 5. LockSupport的parkUntil方法,带有时间
- TERMINATED: 线程中止的状态,这个线程已经完整地执行了它的任务。
其实知识点还可以扩开,把调度算法,内存分配等知识点扩开讲的。