本文涉及到进程、线程、以及线程中断相关问题。
1 进程/线程的上下文切换
计算机为了更充分的利用资源,支持多余CPU数量的任务同时进行。因此需要知道每个任务从哪里加载,又从哪里开始运行。因此,就需要程序计数器和CPU寄存器(上下文)。
- CPU 寄存器是 CPU 内置的容量小、但速度极快的内存。
- 程序计数器则是用来存储 CPU 正在执行的指令位置、或者即将执行的下一条指令位置。
上下文切换:就是将这些CPU寄存器和程序计数器保存起来,运行新的任务的新的指令,最后在回过头来继续运行原先的指令。
进程上下文切换,线程上下文切换,中断上线文切换
带来的问题: 进程上线文切换的时候,每次切换大概需要几十纳秒,在大量的切换下,会耗时在寄存器,内核栈,等的资源保存和恢复上,这也是导致平均负载升高的一个原因。
线程与进程最大的区别在于:线程是调度的基本单位,而进程则是资源拥有的基本单位。说白了,所谓内核中的任务调度,实际上的调度对象是线程;而进程只是给线程提供了虚拟内存、全局变量等资源。
所以,对于线程和进程,我们可以这么理解: - 当进程只有一个线程时,可以认为进程就等于线程。 - 当进程拥有多个线程时,这些线程会共享相同的虚拟内存和全局变量等资源。这些资源在上下文切换时是不需要修改的。 - 另外,线程也有自己的私有数据,比如栈和寄存器等,这些在上下文切换时也是需要保存的。
2 操作系统线程和java线程区别
用户态的线程、内核态的线程、线程的源码
2.1 用户态的线程
1 一开始,是没有线程的概念的,进程就是调度的基本单位。
2 后来,在用户空间,开始有了多线程的概念, 实际上是操作系统按进程维度来调度,操作系统是不去管你用户线程的切换的,应用程序自己在用户空间实现线程的创建、维护和调度。 操作系统只能看到进程,看不到线程。在操作系统看来,每一个进程只有一个线程。
缺点:CPU的切换是以进程为维度的,如果某个线程耗时过长,会阻塞整个进程,即使该进程中还有其他线程在工作。
优点:使用库函数来实现的线程切换,就免去了用户态到内核态的切换。
2.2 内核态的线程
java1.2后,java的线程是依赖操作系统来实现的,是1:1的关系。现在的Java中线程的本质,其实就是操作系统中的线程(轻量级进程)。
线程太耗资源,后来,出现了协程的概念,即是在用户态做线程资源切换,也让操作系统在内核层做线程调度。
协程跟操作系统的线程是有映射关系的,例如我们建了m个协程,需要在N个线程上执行,这就是m: n的方案,这n个线程也是靠操作系统调度实现。
另外协程是按需使用栈内存的,所以理论上可以轻轻松松创建百万级的协程。
目前协程这块支持的最好的是go语言, 不过现在OpenJDK社区也正在为JDK增加协程的支持。
模型:多对一模型、一对一模型、多对多模型
虚拟机中的线程状态,不反应任何操作系统中的线程状态。因为存在多种模型。 现今 Java 中线程的本质,其实就是操作系统中的线程,其线程库和线程模型很大程度上依赖于操作系统(宿主系统)的具体实现。