进程、线程、协程的上下文切换
进程(Process)
一、是什么?
进程是操作系统分配资源(CPU、内存、I/O等)的基本单位。每个进程拥有独立的虚拟地址空间、文件描述符和安全上下文,相当于一个独立的应用程序实例(如运行中的浏览器或Java程序)。
二、解决什么问题
解决多任务并行执行的需求,实现程序间的隔离性(一个进程崩溃不会影响其他进程),同时管理硬件资源(如CPU、内存)的分配。
三、 上下文内容
- 内存映射:虚拟地址空间(代码、数据、堆栈)
- 硬件状态:CPU寄存器值(PC、SP等)
- 系统资源:打开的文件描述符、网络端口、权限信息
- 进程控制块(PCB):操作系统维护的元数据(进程ID、状态、优先级)
四、切换时的具体过程
- 保存上下文:CPU将当前进程的寄存器状态(程序计数器、栈指针等)、内存管理单元(MMU)信息保存到进程控制块(PCB)中。
- 更新调度队列:操作系统将当前进程移出运行队列,根据调度算法(如轮询、优先级)选择下一个进程。
- 加载新上下文:从新进程的PCB中恢复寄存器状态和MMU配置(切换虚拟地址空间)。
- 刷新缓存:清空TLB(快表)避免地址冲突,可能导致性能下降。
- 权限检查:切换内核态/用户态(如果需要)。
耗时:通常需要几微秒到毫秒级,涉及两次内核态切换(保存旧进程 + 加载新进程)。
五、应用场景
- 运行独立应用程序(如同时启动IDE和数据库)
- 需要高隔离性的任务(如安全沙箱)
- 利用多核CPU的并行计算
线程(Thread)
一、是什么?
线程是进程内的执行单元,共享进程的内存和资源(堆、文件句柄),但拥有独立的栈和寄存器。一个进程可包含多个线程(如Java中的Thread
类)。
二、解决什么问题
解决进程内并发执行的效率问题:
- 更轻量级切换(共享内存,无需切换地址空间)
- 更低通信成本(通过共享内存而非IPC)
- 更细粒度的任务并行
三、 上下文内容
- 线程私有:栈、程序计数器(PC)、寄存器值
- 线程共享:进程的堆、全局变量、静态变量
- 线程控制块(TCB):线程ID、状态、优先级(Java通过
java.lang.Thread
类管理)
四、切换时的具体过程
- 保存上下文:CPU保存当前线程的寄存器状态(程序计数器、栈指针等)到线程控制块(TCB)。
- 调度选择:操作系统(或JVM)从就绪队列选择同一进程内的下一个线程。
- 加载新上下文:从TCB恢复新线程的寄存器状态。
- 无需切换地址空间:线程共享进程的虚拟地址空间,跳过MMU/TLB刷新。
耗时:通常1-10微秒,比进程切换快10倍以上(因无需内存空间切换)。
五、应用场景
- Web服务器处理并发请求(如Tomcat线程池)
- GUI应用保持界面响应(后台线程处理任务)
- 并行计算(如Java的
ForkJoinPool
)
五、Java示例
java
// JDK21+ 虚拟线程(轻量级线程实现)
Thread virtualThread = Thread.startVirtualThread(() -> {
System.out.println("Running in virtual thread!");
});
协程(Coroutine)
一、是什么?
协程是用户态的轻量级线程,由程序自身调度(非操作系统),在单线程内实现多任务切换。Java中通过Project Loom的虚拟线程(JDK19+)原生支持。
二、解决什么问题
解决传统线程的瓶颈:
- 消除内核态切换开销(完全在用户态调度)
- 支持超高并发(单机可创建百万级协程)
- 简化异步代码(用同步写法实现异步逻辑)
三、 上下文内容
- 栈帧:仅保存当前方法的局部变量和返回地址
- 寄存器快照:PC、栈指针等少量寄存器
- 挂起点状态:暂停时的代码位置和变量值(通过Continuation保存)
四、切换时的具体过程
- 主动让出控制权:协程通过
yield()
主动暂停,保存当前栈帧和寄存器状态到堆内存。 - 调度器选择:用户级调度器(如JVM的
ForkJoinPool
)选择下一个可运行协程。 - 恢复执行:从堆中加载目标协程的上下文到当前线程栈。
- 无系统调用:全程在用户态完成,无内核参与。
耗时:纳秒级,比线程切换快100倍以上。
五、应用场景
- 高并发IO密集型服务(如10万+连接的微服务)
- 响应式编程(如Spring WebFlux)
- 游戏服务器(处理大量玩家状态)
六、Java示例(JDK21)
java
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
executor.submit(() -> {
System.out.println("Virtual thread 1");
Thread.yield(); // 主动让出控制权
});
executor.submit(() -> System.out.println("Virtual thread 2"));
}
三者的核心区别
特性 | 进程 | 线程 | 协程(虚拟线程) |
---|---|---|---|
隔离性 | 高(独立内存空间) | 中(共享进程内存) | 低(共享线程栈) |
切换开销 | 高(μs~ms级) | 中(μs级) | 极低(ns级) |
切换者 | 操作系统内核 | 操作系统内核 | 用户程序/JVM |
并发量 | 数百个 | 数千个 | 百万级 |
内存占用 | MB级 | KB级 | KB级(可浮动栈) |
Java支持 | ProcessBuilder | java.lang.Thread | java.lang.VirtualThread (JDK19+) |
重要注意事项
- 协程适用场景:协程在IO密集型任务中优势显著,但CPU密集型任务仍需传统线程利用多核。
- 虚拟线程限制:JDK虚拟线程中,
synchronized
可能阻塞载体线程,应改用ReentrantLock
。 - 进程间通信:进程需IPC(管道/Socket),线程用共享内存,协程直接共享对象引用。
- 调试难度:协程的异步特性可能增加调试复杂度(需IDE支持虚拟线程调试)。
总结
- 进程:资源隔离的基石,适合独立应用,切换开销最大。
- 线程:进程内并发的核心,平衡开销与效率,需注意线程安全。
- 协程(虚拟线程):JDK19+的革命性特性,用同步代码实现异步性能,彻底解决"线程饥饿"问题。
演进趋势:现代Java开发中,虚拟线程正逐步替代线程池(如
ExecutorService
),成为高并发服务的首选方案。其设计哲学是:"用线程的写法,获协程的性能"。